Aller au contenu

Photo

Challenge rating... Talk to me


  • Veuillez vous connecter pour répondre
23 réponses à ce sujet

#1
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
 <buying a round...>

I'd like to hear what systems people have developed to adjust the challenge of their mods.  

For encounters the Bioware system uses a static CR calculated on a creature's stats. This is modified somewhat by equipment.

The Tony K Battle AI uses a Combined Challenge Rating (CCR) to determine henchman (and creature) behavior compared against static thresholds. (log(sum(1.5**CR))/log(1.5)-HitDice(PC))

What other systems are out there for
  • determining how tough opponents are
  • determining behavior
  • determining reward/punishments
  • determining skill/feat difficulty
Specifically, how do you adjust difficulty for your players, up and down?

I have something in mind (actually, two - combat & non), I call Dynamic CR modifier.  But, as you should all know by now, I don't like re-inventing the wheel, and I have no problem at all bowing before someone else's magnificence =)
Usually so they don't notice me fumbling... ;-)

<...with someone else's money pouch>

#2
Pstemarie

Pstemarie
  • Members
  • 2 745 messages
Screwtape's Simple XP v1.07 http://nwvault.ign.c....Detail&id=2399

#3
Shadooow

Shadooow
  • Members
  • 4 470 messages
My 2da based XP system can use variable CR instead real CR, also my unofficial patch allows to decrease cost of the item so you can set creature items to cost 0 so they wont affect CR.

#4
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<eyes...>

Pstemarie wrote...
Screwtape's Simple XP v1.07 http://nwvault.ign.c....Detail&id=2399

Ahhh. Nice. I'm digging into the formulas now.

Just the sort of thing I'm looking for, and all too often miss.

Still, so far, my particular wheel is un-invented :-)

<...light up>

#5
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<lays down his abacus...>

ShaDoOoW wrote...
My 2da based XP system can use variable CR instead real CR, also my unofficial patch allows to decrease cost of the item so you can set creature items to cost 0 so they wont affect CR.

Essentially, you are looking up XP based on a variable CR? How do you determine CR?

<...and eyes the infernal contraption>

#6
Shadooow

Shadooow
  • Members
  • 4 470 messages

Rolo Kipp wrote...

<lays down his abacus...>

ShaDoOoW wrote...
My 2da based XP system can use variable CR instead real CR, also my unofficial patch allows to decrease cost of the item so you can set creature items to cost 0 so they wont affect CR.

Essentially, you are looking up XP based on a variable CR? How do you determine CR?

<...and eyes the infernal contraption>

manually, see I have to manually set CR only for certain lvl 40 bosses and monsters, until then it can be easily adjusted to desired value, if its not possible then I set variable with CR I want to have for this creature

#7
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<painting a big, bristly mustache...>

Ahh, thank you. So you can use algorithmic CR most of the time, but have an over-ride capability in your 2das, right?

<...over the pretty little picture>

#8
Shadooow

Shadooow
  • Members
  • 4 470 messages

Rolo Kipp wrote...

<painting a big, bristly mustache...>

Ahh, thank you. So you can use algorithmic CR most of the time, but have an over-ride capability in your 2das, right?

<...over the pretty little picture>

right except the overriding capability is in script, in 2da there is only everything else (base XP per CR, XP modifier for lower level than CR, xp modifier than higher than CR, xp modifier for party etc.)

#9
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<nods his...>

ShaDoOoW wrote...
right except the overriding capability is in script, in 2da there is only everything else (base XP per CR, XP modifier for lower level than CR, xp modifier than higher than CR, xp modifier for party etc.)

Right. As it should be, the *dynamic* part is in script. The default is in 2da.

<...understanding>

#10
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Hrm. Well, there's lots of ways to skin that cat.

For some stuff, we have paragon creatures - scaled up versions of the normal creatures, which depend on player power - the number of times they've completed one of the end runs on the server, the Asmodeus fight. They operate by altering variables, which are then acted on, all in onspawn, by our ApplyLocals function, which applies effects, sets ab, and more. I'll put up the two relevant scripts here for your perusal:
Paragon Scaling
Locals

My most recent project has been a scaling of summons. Unlike the paragons, we're not scaling weapon damages on them, but everything else is subject to scaling, from ab to ac. This is especially ambitious, given that most of the 40 and under creatures don't use ApplyLocals normally, and have an altogether different scaling. Here's that (incomplete) code, which gets even more nitty gritty, adjusting ab and ac to exact parameters, so that we can scale a level 1 creature up to 60 and vice versa.

Summoning Spells script

It'll eventually fire for every summoning spell on the server, including the custom ones, though right now only summon creature 1-9 and the 40+ section of planar binding are done, and I'm still hammering on the scaling function to get it as close to accurate as feasible.

Those are just two examples of scaling, both relying on engine hacks to modify ability scores, ab, and more. You can do much simpler stuff, like this, something another of our devs, Werehound, hammered out as a sort of rough draft:
void ScaleSummon(object oMon, int nPower)
{
    effect eScan;
    for (eScan = GetFirstEffect(oMon); GetIsEffectValid(eScan); eScan = GetNextEffect(oMon))
        if (GetEffectType(eScan) != EFFECT_TYPE_VISUALEFFECT)
            RemoveEffect(oMon, eScan);

    int i, nDigit0, nDigit1, nDigit2, nDigit3, nDigit4, nDigit5, nDigit6, nDigit7, nDigit8, nDigit9;

    SetLocalInt(oMon, "AddAttacks", GetLocalInt(oMon, "AddAttacks") + nPower / 10);
    SetLocalInt(oMon, "Conceal", GetLocalInt(oMon, "Conceal") + 2 * nPower);
    if (GetLocalInt(oMon, "SR") > 0)
        SetLocalInt(oMon, "SR", GetLocalInt(oMon, "SR") + nPower * 2);
    if (GetLocalInt(oMon, "Regen") > 0)
        SetLocalInt(oMon, "SR", GetLocalInt(oMon, "SR") + nPower * 5);
    SetLocalInt(oMon, "DeflectionAC", GetLocalInt(oMon, "DeflectionAC") + nPower/5);
    SetLocalInt(oMon, "DodgeAC", GetLocalInt(oMon, "DodgeAC") + nPower/5);
    SetLocalInt(oMon, "ArmorAC", GetLocalInt(oMon, "ArmorAC") + nPower/5);
    SetLocalInt(oMon, "ShieldAC", GetLocalInt(oMon, "ShieldAC") + nPower/5);
    SetLocalInt(oMon, "NaturalAC", GetLocalInt(oMon, "NaturalAC") + nPower/5);

    int nRes = GetLocalInt(oMon, "PhysImmunities");

    if (nRes) {
        nDigit0 = (nRes % 10);
        nDigit1 = (nRes /= 10) % 10 + nPower/5;
        nDigit2 = (nRes /= 10) % 10 + nPower/5;
        nDigit3 = (nRes /= 10) % 10 + nPower/5;
        nDigit4 = (nRes /= 10) % 10 + nPower/5;
        nDigit5 = (nRes /= 10) % 10 + nPower/5;
        nDigit6 = (nRes /= 10) % 10 + nPower/5;
        nDigit7 = (nRes /= 10) % 10 + nPower/5;
        nDigit8 = (nRes /= 10) % 10 + nPower/5;
        nDigit9 = (nRes /= 10) % 10 + nPower/5;
   }
   nRes = (nRes > 9 ? 9 : nRes < 0 ? 0 : nRes);
   nRes = nRes * 10 + (nDigit8 > 9 ? 9 : nDigit8 < 0 ? 0 : nDigit8);
   nRes = nRes * 10 + (nDigit7 > 9 ? 9 : nDigit7 < 0 ? 0 : nDigit7);
   nRes = nRes * 10 + (nDigit6 > 9 ? 9 : nDigit6 < 0 ? 0 : nDigit6);
   nRes = nRes * 10 + (nDigit5 > 9 ? 9 : nDigit5 < 0 ? 0 : nDigit5);
   nRes = nRes * 10 + (nDigit4 > 9 ? 9 : nDigit4 < 0 ? 0 : nDigit4);
   nRes = nRes * 10 + (nDigit3 > 9 ? 9 : nDigit3 < 0 ? 0 : nDigit3);
   nRes = nRes * 10 + (nDigit2 > 9 ? 9 : nDigit2 < 0 ? 0 : nDigit2);
   nRes = nRes * 10 + (nDigit1 > 9 ? 9 : nDigit1 < 0 ? 0 : nDigit1);
   nRes = nRes * 10 + (nDigit0 > 9 ? 9 : nDigit0 < 0 ? 0 : nDigit0);
   SetLocalInt(oMon, "PhysImmunities", nRes);

    // Modification of abilities handles additional AB, saves, AC
   int nAbility = GetLocalInt(oMon, "Ability");

    nDigit0 = (nAbility % 10) + nPower/2;
    nDigit1 = (nAbility /= 10) % 10 + nPower/2;
    nDigit2 = (nAbility /= 10) % 10 + nPower/2;
    nDigit3 = (nAbility /= 10) % 10 + nPower/2;
    nDigit4 = (nAbility /= 10) % 10 + nPower/2;
    nDigit5 = (nAbility /= 10) % 10 + nPower/2;

   if (nDigit5 < 0) ModifyAbilityScore(oMon, 0, nDigit5);
   if (nDigit4 < 0) ModifyAbilityScore(oMon, 1, nDigit4);
   if (nDigit3 < 0) ModifyAbilityScore(oMon, 2, nDigit3);
   if (nDigit2 < 0) ModifyAbilityScore(oMon, 3, nDigit2);
   if (nDigit1 < 0) ModifyAbilityScore(oMon, 4, nDigit1);
   if (nDigit0 < 0) ModifyAbilityScore(oMon, 5, nDigit0);

   if (nAbility > 9)
   nAbility = nAbility * 10 + (nDigit4 > 9 ? 9 : nDigit4 < 0 ? 0 : nDigit4);
   nAbility = nAbility * 10 + (nDigit3 > 9 ? 9 : nDigit3 < 0 ? 0 : nDigit3);
   nAbility = nAbility * 10 + (nDigit2 > 9 ? 9 : nDigit2 < 0 ? 0 : nDigit2);
   nAbility = nAbility * 10 + (nDigit1 > 9 ? 9 : nDigit1 < 0 ? 0 : nDigit1);
   nAbility = nAbility * 10 + (nDigit0 > 9 ? 9 : nDigit0 < 0 ? 0 : nDigit0);
   SetLocalInt(oMon, "Ability", nAbility);

    int nSkill = GetLocalInt(oMon, "Skill");

    if (nSkill) {
        nDigit0 = (nSkill % 10) + nPower * 3 / 2;
        nDigit1 = (nSkill /= 10) % 10 + nPower * 3 / 2;
        nDigit2 = (nSkill /= 10) % 10 + nPower * 3 / 2;
        nDigit3 = (nSkill /= 10) % 10 + nPower * 3 / 2;
        nDigit4 = (nSkill /= 10) % 10 + nPower * 3 / 2;
        nDigit5 = (nSkill /= 10) % 10 + nPower * 3 / 2;
        nDigit6 = (nSkill /= 10) % 10 + nPower * 3 / 2;
        nDigit7 = (nSkill /= 10) % 10 + nPower * 3 / 2;
        nDigit8 = (nSkill /= 10) % 10 + nPower * 3 / 2;
   }
   nSkill = (nSkill > 9 ? 9 : nSkill < 0 ? 0 : nSkill);
   nSkill = nSkill * 10 + (nDigit7 > 9 ? 9 : nDigit7 < 0 ? 0 : nDigit7);
   nSkill = nSkill * 10 + (nDigit6 > 9 ? 9 : nDigit6 < 0 ? 0 : nDigit6);
   nSkill = nSkill * 10 + (nDigit5 > 9 ? 9 : nDigit5 < 0 ? 0 : nDigit5);
   nSkill = nSkill * 10 + (nDigit4 > 9 ? 9 : nDigit4 < 0 ? 0 : nDigit4);
   nSkill = nSkill * 10 + (nDigit3 > 9 ? 9 : nDigit3 < 0 ? 0 : nDigit3);
   nSkill = nSkill * 10 + (nDigit2 > 9 ? 9 : nDigit2 < 0 ? 0 : nDigit2);
   nSkill = nSkill * 10 + (nDigit1 > 9 ? 9 : nDigit1 < 0 ? 0 : nDigit1);
   nSkill = nSkill * 10 + (nDigit0 > 9 ? 9 : nDigit0 < 0 ? 0 : nDigit0);
   SetLocalInt(oMon, "Skill", nSkill);

    string sSoak = GetLocalString(oMon, "Soak"), sNewSoak = "";

    if (sSoak != "") {
        struct SubString ss, spss;
        int nSoak, nPlus;
        ss.rest = sSoak;

        while (ss.rest != "") {
            ss = GetFirstSubString(ss.rest, " ");

            if (ss.first != "0+0" && ss.first != "0") {
                spss = GetFirstSubString(ss.first, "+");

                nSoak = StringToInt(spss.first) + nPower*2;
                nPlus = (nPlus + StringToInt(spss.rest) + nPower/5  > 6 ? nPlus + StringToInt(spss.rest) + nPower/5 : nPlus + StringToInt(spss.rest) + nPower/5 + 1);
                if (nSoak < 0) nSoak = 0;
                if (nPlus < 0) nPlus = 0;
            }
            sNewSoak += IntToString(nSoak) + "+" + IntToString(nPlus) + " ";
        }
    }
    SetLocalString(oMon, "Soak", sNewSoak);

    nRes = GetLocalInt(oMon, "EnergyImmunities");
    if (nRes) {
        nDigit0 = (nRes % 10);
        nDigit1 = (nRes /= 10) % 10 + nPower/8;
        nDigit2 = (nRes /= 10) % 10 + nPower/8;
        nDigit3 = (nRes /= 10) % 10 + nPower/8;
        nDigit4 = (nRes /= 10) % 10 + nPower/8;
        nDigit5 = (nRes /= 10) % 10 + nPower/8;
        nDigit6 = (nRes /= 10) % 10 + nPower/8;
        nDigit7 = (nRes /= 10) % 10 + nPower/8;
        nDigit8 = (nRes /= 10) % 10 + nPower/8;
        nDigit9 = (nRes /= 10) % 10 + nPower/8;
        nRes = (nRes > 9 ? 9 : nRes < 0 ? 0 : nRes);
       nRes = nRes * 10 + (nDigit8 > 9 ? 9 : nDigit8 < 0 ? 0 : nDigit8);
       nRes = nRes * 10 + (nDigit7 > 9 ? 9 : nDigit7 < 0 ? 0 : nDigit7);
       nRes = nRes * 10 + (nDigit6 > 9 ? 9 : nDigit6 < 0 ? 0 : nDigit6);
       nRes = nRes * 10 + (nDigit5 > 9 ? 9 : nDigit5 < 0 ? 0 : nDigit5);
       nRes = nRes * 10 + (nDigit4 > 9 ? 9 : nDigit4 < 0 ? 0 : nDigit4);
       nRes = nRes * 10 + (nDigit3 > 9 ? 9 : nDigit3 < 0 ? 0 : nDigit3);
       nRes = nRes * 10 + (nDigit2 > 9 ? 9 : nDigit2 < 0 ? 0 : nDigit2);
       nRes = nRes * 10 + (nDigit1 > 9 ? 9 : nDigit1 < 0 ? 0 : nDigit1);
       nRes = nRes * 10 + (nDigit0 > 9 ? 9 : nDigit0 < 0 ? 0 : nDigit0);
       SetLocalInt(oMon, "EnergyImmunities", nRes);
   }


   nRes = GetLocalInt(oMon, "EnergyVulnerabilities");
    if (nRes || nPower < 0) {
        nDigit0 = (nRes % 10);
        nDigit1 = (nRes /= 10) % 10 + -nPower/8;
        nDigit2 = (nRes /= 10) % 10 + -nPower/8;
        nDigit3 = (nRes /= 10) % 10 + -nPower/8;
        nDigit4 = (nRes /= 10) % 10 + -nPower/8;
        nDigit5 = (nRes /= 10) % 10 + -nPower/8;
        nDigit6 = (nRes /= 10) % 10 + -nPower/8;
        nDigit7 = (nRes /= 10) % 10 + -nPower/8;
        nDigit8 = (nRes /= 10) % 10 + -nPower/8;
        nDigit9 = (nRes /= 10) % 10 + -nPower/8;
        nRes = (nRes > 9 ? 9 : nRes < 0 ? 0 : nRes);
        nRes = nRes * 10 + (nDigit7 > 9 ? 9 : nDigit7 < 0 ? 0 : nDigit7);
        nRes = nRes * 10 + (nDigit6 > 9 ? 9 : nDigit6 < 0 ? 0 : nDigit6);
        nRes = nRes * 10 + (nDigit5 > 9 ? 9 : nDigit5 < 0 ? 0 : nDigit5);
        nRes = nRes * 10 + (nDigit4 > 9 ? 9 : nDigit4 < 0 ? 0 : nDigit4);
        nRes = nRes * 10 + (nDigit3 > 9 ? 9 : nDigit3 < 0 ? 0 : nDigit3);
        nRes = nRes * 10 + (nDigit2 > 9 ? 9 : nDigit2 < 0 ? 0 : nDigit2);
        nRes = nRes * 10 + (nDigit1 > 9 ? 9 : nDigit1 < 0 ? 0 : nDigit1);
        nRes = nRes * 10 + (nDigit0 > 9 ? 9 : nDigit0 < 0 ? 0 : nDigit0);
        SetLocalInt(oMon, "EnergyVulnerabilities", nRes);
   }


    nRes = GetLocalInt(oMon, "EnergyResistance");
    if (nRes) {
        nDigit0 = (nRes % 10) + nPower / 5;
        nDigit1 = (nRes /= 10) % 10 + nPower / 5;
        nDigit2 = (nRes /= 10) % 10 + nPower / 5;
        nDigit3 = (nRes /= 10) % 10 + nPower / 5;
        nDigit4 = (nRes /= 10) % 10 + nPower / 5;
        nDigit5 = (nRes /= 10) % 10 + nPower / 5;
        nDigit6 = (nRes /= 10) % 10 + nPower / 5;
        nDigit7 = (nRes /= 10) % 10 + nPower / 5;
        nDigit8 = (nRes /= 10) % 10 + nPower / 5;
        nRes = (nRes > 9 ? 9 : nRes < 0 ? 0 : nRes);
       nRes = nRes * 10 + (nDigit7 > 9 ? 9 : nDigit7 < 0 ? 0 : nDigit7);
       nRes = nRes * 10 + (nDigit6 > 9 ? 9 : nDigit6 < 0 ? 0 : nDigit6);
       nRes = nRes * 10 + (nDigit5 > 9 ? 9 : nDigit5 < 0 ? 0 : nDigit5);
       nRes = nRes * 10 + (nDigit4 > 9 ? 9 : nDigit4 < 0 ? 0 : nDigit4);
       nRes = nRes * 10 + (nDigit3 > 9 ? 9 : nDigit3 < 0 ? 0 : nDigit3);
       nRes = nRes * 10 + (nDigit2 > 9 ? 9 : nDigit2 < 0 ? 0 : nDigit2);
       nRes = nRes * 10 + (nDigit1 > 9 ? 9 : nDigit1 < 0 ? 0 : nDigit1);
       nRes = nRes * 10 + (nDigit0 > 9 ? 9 : nDigit0 < 0 ? 0 : nDigit0);
       SetLocalInt(oMon, "EnergyResistance", nRes);
    }

}

It all depends exactly how accurate you need the scaling to be. Hell, right now we're debating decreasing the SR increase on paragon spawns, and those have been that way for years.

Funky

#11
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<breaking into...>

Whew! Finally made me start a "FS_HigherGround" folder in my script archive! I'm simply getting too many good things from you :-)

Ok, I follow *how* you are scaling things (and it doesn't get more dynamic than in variables :-P ) but how are you determining *what* to scale to? In Werehound's example, how does he determine nPower?

Let me expand a bit on what I'm hoping to do: Player X has played pure fighters through epic levels and is a *juggernaut*... with a fighter. She recently decided to try a roguish-wizard, but she's having a pretty tough time getting the hang of it. I'd like a system (sensor/decider/executor/feedback) that dynamically adjusts difficulty based on how the player plays, rather than a predetermined function of how a player *ought* to play. If they are fumbling a bit, ease off. If they are burning through bosses, pour the oil on!

So, you adjust CR (the more I read these scripts (currently fine-combing ai_locals), the more awed I am :-). That's the executor. How do you sense the player's difficulty? How do you decide *what* to adjust it to. And how do you feedback into the system so it converges on an optimal modifier?

Not looking for proprietary scripts, btw :-) Just ideas. But I learn so *much* from your generosity! Thank you.

<...a cold sweat>

#12
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<perks up...>

Hey, you have a lemure appearance? Is it HG specific?

Oops, sorry. OT. But someone was looking for one somewhere... <oh, *that's* specific!>

<...digging back into the "paragon" scroll>

#13
Pstemarie

Pstemarie
  • Members
  • 2 745 messages
Good lord FS, I think I just had an aneuirsm trying to follow all that...

I wonder sometimes about the prudence of all these complex architectures. Is it a big drain on CPU resources to run all this stuff? Not trying to knitpick here - just looking at it from the perspective of someone that has VERY rudimentary scripting knowledge.

#14
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

Pstemarie wrote...

Good lord FS, I think I just had an aneuirsm trying to follow all that...

I wonder sometimes about the prudence of all these complex architectures. Is it a big drain on CPU resources to run all this stuff? Not trying to knitpick here - just looking at it from the perspective of someone that has VERY rudimentary scripting knowledge.

This is the shortest answer, so I'll tackle it first. The answer is: not remotely. You have to get way more complex than that before you hit load issues, or do some seriously heavy lifting. It's always prudent to keep efficiency in mind, and we DID have some short-lived issues with a few spawns appearing invisible (not rendering - very odd, because you'd think that'd be a client-side only phenomenon, but it wasn't) when we added to that code a while back, but generally speaking, you can get away with a lot more than you'd expect - the limit I run into time and again is not the computer, but my own grasp of the complexities involved, and my willingness to spend the time thinking through the layers of code. For a long time when I was learning I would shy away from complex code, using the excuse that doing something with additional precision would be needless load. That's generally what that is, though - an excuse. :P Of course, additional precision isn't always an effective use of development time - that's on you to decide, naturally. I'm still gritting my teeth over the ModifyNPCAttackBonus function from that summon scaling script, which STILL isn't done... :D

As a caveat, I should say that we always run at quadruple the normal TMI limit (normal is 0x20000 or ~131k instructions, we run at a 524287 for most things), occasionally bumping it up from that to 4 million (4*1024*1024, actually) to do heavy lifting (mainly on modload). There, load STILL isn't an issue - the hardcoded instruction limit is, though there are all sorts of ways to circumvent it, even if you're not using the NWNX TMI plugin. Of course, your milage may vary - it depends on what sort of hardware you run on. We're fortunate enough to be on a server admin's setup, with 32G of ram, among other absurdities (that machine hosts 13 instances and an SQL server, and some unrelated programs - typically only 10-18G of ram are devoted to our ops). The only lag we get is 15 minutes a night when another system on the connection runs a backup (as well as an hour-long stint every two weeks or so, for a longer backup).

Anyway, aneurisms aside, if you want a chuckle, use the Omnibus to search up some of my old posts from 2004. I'm self taught, remember, and I was more clueless than most here, then. Anyone can do this stuff, with enough patience and practice.

Funky

#15
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

Rolo Kipp wrote...

<breaking into...>

Whew! Finally made me start a "FS_HigherGround" folder in my script archive! I'm simply getting too many good things from you :-)

Ok, I follow *how* you are scaling things (and it doesn't get more dynamic than in variables :-P ) but how are you determining *what* to scale to? In Werehound's example, how does he determine nPower?

He actually left that to me to decide. I figured it out by looking at where our spawns at various levels were at, and then determining what changes to make at what levels. I won't lie, it was pretty time-intensive. Happily, I already had the scalings for Legendary and Paragon levels, from the CoreStat tab of that spreadsheet I linked you in the pm, so I just had to work out where 1-40 creatures should be. I looked at a bunch of 1-5 level critters, then looked at our level 41 stats, and worked out a scaling to get from a to b. I also looked at familiars at 1 and 40 too, so I could figure out when to start scaling them up (decided level 5), and by how much (dynamically scaling them is going to be SOOO much better than 2da edits and utcs).

Without having that data, which is harvested from a lot of playtest and player feedback based on the specifics of our PW (what ab, ac, etc players are generally at a x level), it's very very hard to guestimate.

The results of those measurings are contained in the SummonCalcSingleLevelStats function. There's no guarantee yours will look anything like ours, but here it is all the same, in case it's of any help (you can also find it in the fky_summon_inc above, shortly to be renamed hgs_gen_summon, since it's no longer just an include):

struct ModifySummon SummonCalcSingleLevelStats(struct ModifySummon ms, int nCurrentLevel, int nSubtract, int nRogueSkills) {
    if (nCurrentLevel > (79+nSubtract) || nCurrentLevel < (1+nSubtract)) {
        WriteTimestampedLogEntry("ERROR: SummonCalcSingleLevelStats level out of bounds!");
        return ms;
    }
    if (nCurrentLevel < (40+nSubtract)) {
        ms.enh            += 0.25;
        ms.ab             += 2.23;
        ms.ac             += 1.43;
        ms.skill          += 1.94;
        if (nRogueSkills)
            ms.rgskill    += 1.0;
        ms.save           += 0.94;
        ms.sr             += 0.56;
        if (nCurrentLevel > (20+nSubtract)) {
            ms.phsimm     += 0.15;
            ms.eleimm     += 0.55;
            if (nCurrentLevel > (30+nSubtract)) {
                ms.eleres += 0.1;
                ms.exoimm += 0.0;
                ms.exores += 0.0;
                ms.hp     += 44;
            } else
                ms.hp     += 25;
        } else
            ms.hp         += 15;

    } else if (nCurrentLevel < (60+nSubtract)) {
        if (nCurrentLevel == (59+nSubtract) || nCurrentLevel == (40+nSubtract))
            ms.enh        += 1.0;
        else
            ms.enh        += 0.17;
        ms.ab             += 0.6;
        ms.ac             += 0.6;
        ms.skill          += 2.0;
        if (nRogueSkills)
            ms.rgskill    += 1.0;
        ms.save           += 0.6;
        ms.sr             += 1.0;
        ms.phsimm         += 0.15;
        ms.eleimm         += 1.0;
        ms.eleres         += 0.5;
        if (nCurrentLevel == (47+nSubtract))
            ms.exoimm     += 1.0;
        else if (nCurrentLevel > (47+nSubtract))
            ms.exoimm     += 1.0;
        ms.exores         += 0.25;
        ms.hp             += 50;

    } else if (nCurrentLevel < (80+nSubtract)) {
        if (nCurrentLevel == (64+nSubtract))
            ms.enh        += 1.0;
        ms.ab             += 0.6;
        ms.ac             += 0.6;
        ms.skill          += 0.5;
        ms.rgskill        += 0.0;
        ms.save           += 0.4;
        ms.sr             += 0.25;
        ms.phsimm         += 0.15;
        ms.eleimm         += 0.4;
        ms.eleres         += 0.4;
        ms.exoimm         += 0.2;
        ms.exores         += 0.25;
        ms.hp             += 100;
    }
    return ms;
}

Anyway, moving on...

Let me expand a bit on what I'm hoping to do: Player X has played pure fighters through epic levels and is a *juggernaut*... with a fighter. She recently decided to try a roguish-wizard, but she's having a pretty tough time getting the hang of it. I'd like a system (sensor/decider/executor/feedback) that dynamically adjusts difficulty based on how the player plays, rather than a predetermined function of how a player *ought* to play. If they are fumbling a bit, ease off. If they are burning through bosses, pour the oil on!


Ok, that should be much easier than what I'm doing. I'm scaling to hit certain benchmarks, and you don't have to. You just need various stepping stones, like our paragon code, to make 'harder', or, conversely, 'easier'. You could do something very like what Werehound was.

I would scale difficulty a number of ways, depending on how clever you want to get. One simple metric is how long it takes the player to kill something. That's pretty easy to measure - just increment RoundsSpawned in Each EndCombatRound, and record somewhere OnDeath. If deaths are taking longer than X rounds - up to you how long they should take - reduce difficulty. If shorter, add. That's essentially what difficulty chalks up to - time per kill. You might also want to put in a count on heartbeat, and subtract a bit of difficulty after x beats, in case the creature kills the pc and they wind up having to respawn (blocking incrementing from the creature's OnDeath).

Of course, you don't have to up difficulty on every increase of the counter - you could do it every 5, 10, whatever seems to work in playtest.

A word of warning: if the players figure out what you're doing, this could be eminently exploitable - though I don't suppose it'd matter if you're talking single player. Might want to keep it somewhat hush-hush - there's no easy way for them to tell what you're doing.

So, you adjust CR (the more I read these scripts (currently fine-combing ai_locals), the more awed I am :-). That's the executor. How do you sense the player's difficulty? How do you decide *what* to adjust it to. And how do you feedback into the system so it converges on an optimal modifier?

We do it by counting the number of times they've completed our demi-god-hood quest. That gives them a +2 to all stats the first time they complete it, and a reduction in environmental penalties the next 3 after that, so it's a fair approximation of increased power from the level 60 norm. Players refer to them as 'demi-iterations', which is as good a name as any, and with our party limit of 10 pcs, you can get up to 50 of them, at which point a larger percentage of your spawns are P(aragon)1, 2, or even 3. We've scaled them so they they ensure a little risk to a run - the wrong combination of paragons can SERIOUSLY challenge even the most uber party of vets.There is a certain sadistic glee to be had from watching players scurry frantically around trying to avoid getting flattened...but I digress. :devil:

Anyway, long story short, it takes guesswork and testing, but if you do it incrementally, and base it on time/kill, you're unlikely to have many issues. Just remember to account for all classes' strengths and weakness. Mages, increase/decrease enemy SR, saves, and hp (more hp means more spells burnt, though more hp works well enough for many classes). Rogues, if you lack the plugins to do % crit resistance or to split sneak and crit immune, you can scale up and down the chance of enemy crit immunity. Fighters, enemy ac and discipline. And so on. Most classes will have a harder time when you add elemental resists, if you have any ele damage on your weapons (casters have larger damage packets, so are generally hurt more by immunes rather than resists).

Not looking for proprietary scripts, btw :-) Just ideas. But I learn so *much* from your generosity! Thank you.

You've already seen all the relevant proprietary scripts. :P LMK if you have any more questions.

Funky

#16
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages

Rolo Kipp wrote...

<perks up...>

Hey, you have a lemure appearance? Is it HG specific?

Oops, sorry. OT. But someone was looking for one somewhere... <oh, *that's* specific!>

<...digging back into the "paragon" scroll>


We used Mane Man 2 (CODI)* - not that close, but hey.

Funky

#17
Pstemarie

Pstemarie
  • Members
  • 2 745 messages
Thanks FS for the explanation. It has given me somewhat of an idea about where scripting limitations may lie. I've always been hesitant to get into complex coding because my past endeavours have been somewhat...erm...critical failures. Seeing glimpses of what you guys have done at HG helps keep my motivation alive...

#18
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<squinting at his cards...>

FunkySwerve wrote...
...
He actually left that to me to decide. I figured it out by looking at where our spawns at various levels were at, and then determining what changes to make at what levels. I won't lie, it was pretty time-intensive.

Ahhh, that seems to be the standard approach. adjust->playtest->adjust iterations.  Not trying to *replace* that, but I think I have an idea that will help things a bit :-)

Happily, I already had the scalings for Legendary and Paragon levels, from the CoreStat tab of that spreadsheet I linked you in the pm, so I just had to work out where 1-40 creatures should be.

Good stuff! Like so many chaotic feedback systems, getting close to optimum before turning it over to algorithym is pretty damn important :-)

I also looked at familiars at 1 and 40 too, so I could figure out when to start scaling them up (decided level 5), and by how much (dynamically scaling them is going to be SOOO much better than 2da edits and utcs).

I am *soooo* with you on that one.  Do you know that my jaw actually dropped when I saw how Bioware implemented familiars? 40 blueprints each?! Holy moly!

I am currently using, er, damn... lemme look... Ah, MBHenchman Kit for leveling.  Just a placeholder.  I am not happy (in particular) with the scaling of equipment and making the scale class sensitive. So I'll be re-writing that eventually.

...
The results of those measurings are contained in the SummonCalcSingleLevelStats function. There's no guarantee yours will look anything like ours, but here it is all the same, in case it's of any help (you can also find it in the fky_summon_inc above, shortly to be renamed hgs_gen_summon, since it's no longer just an include)

Makes sense to me :-) And I struggled with that portion for a good hour last night, but I got it... eventually. Better, in so many respects, to what I was <vaguely> thinking... :-P

...  
I'd like a system (sensor/decider/executor/feedback) that dynamically adjusts difficulty based on how the player plays, rather than a predetermined function of how a player *ought* to play. If they are fumbling a bit, ease off. If they are burning through bosses, pour the oil on!

Ok, that should be much easier than what I'm doing. I'm scaling to hit certain benchmarks, and you don't have to. You just need various stepping stones, like our paragon code, to make 'harder', or, conversely, 'easier'. You could do something very like what Werehound was.

Actually, I want a *modifier* to the scaling.  Take the normal scaling as the baseline of what I *think* the encounter difficulty should be and then adjust it with the DCRc (Dynamic CR modifieer - combat) or DCRn (non-combat).

I would scale difficulty a number of ways, depending on how clever you want to get. One simple metric is how long it takes the player to kill something. ...

Another one, that is both simpler and provides a feedback mechanism is a ratio of damage given to damage taken, with a weight on top... generally speaking, if the PC deals massive damage while taking very little -> scale up CR. If PC takes too much damage -> scale down. Time of combat is immaterial, except I'll average the DCRc over the last 3 game days to moderate swings in the feedback.

The DCRn would have a similar ratio of xp earned (non-combat) to xp expected. If the PC is earning gobs of non-combat xp, make skill checks harder.  If they can't succeed with even simple tasks, make things easier.

Both those functions converge on a optimal difficulty setting <should, you mean>. Yes, *should* converge on an optimal setting that is controlled by the weight constant on top. For DCRc, making things easier increases damage given, which makes things harder.  The averaging function and the weight moderate swings and push the modifier to a value that suits *that particular PC's* play-style, setting things just *so* difficult, but not worse.

A word of warning: if the players figure out what you're doing, this could be eminently exploitable - though I don't suppose it'd matter if you're talking single player. Might want to keep it somewhat hush-hush - there's no easy way for them to tell what you're doing.

That very sticking point is what led me <by the nose... a very big nose> to this approach.  Power gaming (nothing against it - there is no cheating, only advanced playing techniques (APT!)) is basically playing a game by the meta-rules.  So, whatever I did re:scaling has to be resistant to APT.

To take advantage of the DCRc, a player would have to intentionally take massive damage while dealing very little. And death is permanent on Amethyst. Not a terribly viable option. Still, one could sand-bag minor combats before a boss battle... but that is analogous to keeping an ace up you sleeve and suprising the bugger. Excuse my british :-P

...We've scaled them so they they ensure a little risk to a run - the wrong combination of paragons can SERIOUSLY challenge even the most uber party of vets.There is a certain sadistic glee to be had from watching players scurry frantically around trying to avoid getting flattened...but I digress. :devil: 

No comment =)

Ok, one comment.  The original "Banish V'rax'l" (actual title - "Ouch.") campaign took place in the '80s.  A group of 12 players burned through 93 PCs to accomplish the act (including Rolo, who "died" (as nearly as he can) 7 times). The players would plan strategy meetings (away from me ;-) to plan out their next hour of game time. It took 6 months (Westpac) and 50 years gametime to complete. It was the single most intense and fulfilling campaign I ever ran. Oh. Yeah. One survivor. ;-) Who is now a High One recurring NPC/DM.

...
Just remember to account for all classes' strengths and weakness. Mages, increase/decrease enemy SR, saves, and hp (more hp means more spells burnt, though more hp works well enough for many classes). ...

Yes. class & race context is going to be very important to me.  I do *not* believe in balancing things by making everything work for everyone. :-/ Another pet peeve of mine, OT :-P class and Race, like the PCs death, should *mean* something.

Anyway, thank you very much for all the time and consideration you take ;-) This really does help me.  And hopefully others.

<...and tossing a bit of gold in the pot>

Modifié par Rolo Kipp, 23 octobre 2011 - 04:03 .


#19
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<laughing...>

FunkySwerve wrote...
We used Mane Man 2 (CODI)* - not that close, but hey.

Hey, that's the exact model I thought of when I saw Shadow's suggestion :-P

Great minds think alike :-)
<or, by necessity, lesser minds run in the same rut>
What? What are you babbling about, bird?
<...>

<...his fool head off>

#20
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
The problem with the damage metric is that players don't have a working ondamaged. You can approximate it with onhits, as someone...I think Shadow, outline, but it caps the number of firings per round, which is problem for precision in higher-intensity combat. A search on these forums should turn up the specifics - it was a fairly recent thread.

Funky

Modifié par FunkySwerve, 23 octobre 2011 - 04:23 .


#21
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<looking just a bit...>

And I read that, too. Hmm... listener to capture the damage floaty text? Damage healed?
Grrr... I'll think of something.

<sigh>

<...disconcerted>

#22
ffbj

ffbj
  • Members
  • 593 messages
Plus various conditional things like stunned, poisoned, diseased, could be more important than the actual hit points a player has left. Though I supposed you could do that through a combat rd end check. So you are trying to keep the player on the knife's edge so most combats will neither be too difficult nor too easy. So on a scale of 1-10 1 being trivial and 10 certain death you want most combats in the 5-6-7 range. Philosophically I get it and try to do something similar mostly through creature ai. Some monsters will flee you or hide if you are too much for them, or try other tactics to avoid direct confrontation. Others will pour it on if they think you are weak, though some monsters will, while not entirely ignoring you, act as though you are beneath their contempt, unless you really insist on dying to them
I think the scaling approach is a valid one, though I just usually spawn more creatures, with more frequency to counter tougher PC's and vice verse for lesser PC's.
I think in some ways you can go too far to try and get that perfect match. Some fights will be easier, some harder, and some builds will just work better. If you have a chest that can't be based open, or has a nasty trap destroying all the contents the tough fighter is out of their depth. Also spot/listen search other skills that are class specific should matter. Overall module design and how various character builds do should vary. Though I do get where you are coming from as in my own module some builds are just more effective in fights than others.

#23
Rolo Kipp

Rolo Kipp
  • Members
  • 2 791 messages
<peeking over...>

ffbj wrote...
Plus various conditional things like stunned, poisoned, diseased, could be more important than the actual hit points a player has left. Though I supposed you could do that through a combat rd end check.

Hmmm, I think all those effects are secondary *in the long run*. They help you hurt NPCs and be hurt by NPCs, but I think the damge dealt/taken metric is the primary combat measurement.  I.e. it doesn't matter a hoot if you stun your opponent, if you do not follow it up with damage (or take the opportunity to run like blazes and *avoid* damage ;-).

So you are trying to keep the player on the knife's edge so most combats will neither be too difficult nor too easy. So on a scale of 1-10 1 being trivial and 10 certain death you want most combats in the 5-6-7 range.

Something like that, yes. But not a static, sustained level of difficulty.  I read somewhere (Richard Garriot? Can't remember) that the most addictive "combat rhythm" was something like easy-easy-medium-hard and back to the beginning.

Philosophically I get it and try to do something similar mostly through creature ai. Some monsters will flee you or hide if you are too much for them, or try other tactics to avoid direct confrontation. Others will pour it on if they think you are weak, though some monsters will, while not entirely ignoring you, act as though you are beneath their contempt, unless you really insist on dying to them

Oh, yes.  I will certainly have different *behaviors* for NPCs :-) And just because those goblins are cowards doesn't mean their fussilade of tiny poisoned arrows won't be a *serious* encounter ;-) After all, if strafing & kiting works so well for PCs... it ought to work for NPCs :-) Nothing like a weak, cowardly goblin screaming in fear and running to get the PC rushing headlong into a *proper* ambush.

I think the scaling approach is a valid one, though I just usually spawn more creatures, with more frequency to counter tougher PC's and vice verse for lesser PC's.

That's the execution of the algorythm, how you *apply* the modifier. This, also, should be context sensitive, I think.  With unique/solitary types, you buff them up.  With gang-bangers, you mob them.  If you are not wearing plate, you *never* want to start a blood-frenzy of scragger (land pirhana) in the World's Edge :-) On the other hand, all the armor you can afford will do little against an elder troll with a control gem.

I think in some ways you can go too far to try and get that perfect match. Some fights will be easier, some harder, and some builds will just work better.

Yup. As FS said, it's a question of how much effort/time you want to put into something that may have little impact. Diminishing returns.  Frankly, my creation cycle could never survive commercial development.  I can and do work in that environment, but NwN is *not* work, for me.  I am, therefore, free to be as wasteful of my own resources as I wish <and the gf allows> Ug. That's right. Groceries to day ;-/ <sigh>

If you have a chest that can't be based open, or has a nasty trap destroying all the contents the tough fighter is out of their depth. Also spot/listen search other skills that are class specific should matter. Overall module design and how various character builds do should vary. Though I do get where you are coming from as in my own module some builds are just more effective in fights than others.

The key phrase there is "in fights". I do have a skill overlay in the works that makes non-combat play-styles more viable :-)

And hopefully develops deeper, richer characters who don't mind walking on...

<...the edge>

#24
ffbj

ffbj
  • Members
  • 593 messages
Yeah that's a good analysis and response.
Mostly the poisoned, disease, etc.. was a response to trying to lighten up on the PC if they are in a world of hurt. Yes hp's are the most important, but other negative conditions can result in your hp's dropping like a rock. That's all I was trying to point out.
Just as an example of this sort of need based response, I have divine intervention, which can occur if the PC is over a certain level and is on good terms with their god. It occurs rarely, though it has saved my PC a few times.