Author Topic: Challenge rating... Talk to me  (Read 547 times)

Legacy_Rolo Kipp

  • Hero Member
  • *****
  • Posts: 4349
  • Karma: +0/-0
Challenge rating... Talk to me
« on: October 22, 2011, 07:18:07 pm »


                <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>
               
               

               
            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #1 on: October 22, 2011, 07:36:43 pm »


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

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #2 on: October 22, 2011, 07:54:07 pm »


               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.
               
               

               
            

Legacy_Rolo Kipp

  • Hero Member
  • *****
  • Posts: 4349
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #3 on: October 22, 2011, 07:55:08 pm »


               <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>
               
               

               
            

Legacy_Rolo Kipp

  • Hero Member
  • *****
  • Posts: 4349
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #4 on: October 22, 2011, 07:57:34 pm »


               <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>
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #5 on: October 22, 2011, 08:17:34 pm »


               

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
               
               

               
            

Legacy_Rolo Kipp

  • Hero Member
  • *****
  • Posts: 4349
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #6 on: October 22, 2011, 08:25:01 pm »


               <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>
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #7 on: October 22, 2011, 08:40:07 pm »


               

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.)
               
               

               
            

Legacy_Rolo Kipp

  • Hero Member
  • *****
  • Posts: 4349
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #8 on: October 22, 2011, 09:12:48 pm »


               <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>
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #9 on: October 22, 2011, 10:21:39 pm »


               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
               
               

               
            

Legacy_Rolo Kipp

  • Hero Member
  • *****
  • Posts: 4349
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #10 on: October 22, 2011, 10:43:58 pm »


               <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>
               
               

               
            

Legacy_Rolo Kipp

  • Hero Member
  • *****
  • Posts: 4349
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #11 on: October 22, 2011, 11:02:06 pm »


               <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>
               
               

               
            

Legacy_Pstemarie

  • Hero Member
  • *****
  • Posts: 4368
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #12 on: October 23, 2011, 12:01:44 am »


               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.
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #13 on: October 23, 2011, 07:50:40 am »


               

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. '<img'> 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... '<img'>

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
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
Challenge rating... Talk to me
« Reply #14 on: October 23, 2011, 08:22:31 am »


               

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. '<img'> LMK if you have any more questions.

Funky