simeonpilgrim / coab

Automatically exported from code.google.com/p/coab

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Enable Multiple Dual-Class

GoogleCodeExporter opened this issue · comments

Type-Enhancement

What steps will reproduce the problem?
1. Create human character.
2. Change classes.
3. Advance beyond old class level.

What is the expected output? What do you see instead?
In AD&D 1st Ed the Dual class is implicitly meant for maximum two classes.
In AD&D 2nd Ed this restriction is lifted.
In Curse, the Human Change Classes does not appear if you have an old class
already. In Secret and POD the menu item is there but says the character
"does not qualify".
There are two possible enhancements here:
A) Make the menu item appear and provide appropriate message if the
character does not qualify (e.g. not human, already changed class, level
insufficient, primary attributes insufficient, etc... don't know if there
are more). 
B) Make the game behave like AD&D 2Ed in this aspect, i.e. allow multiple
changes.

What version of the product are you using? On what operating system?
1.0.20 on Win XP

Please provide any additional information below.
References are from SVN dated 11 Sep 2009.
Notes marked with //S:

A) seems like transferring condition checks from here:
ovr018.cs, line 89:
                        if ((gbl.area2_ptr.training_class_mask > 0 ||
Cheats.free_training == true) &&
                            ovr026.IsHuman(gbl.player_ptr) &&

ovr026.HumanFirstOldClass_Unknown(gbl.player_ptr) == ClassId.unknown)
                        {
                            menuFlags[allow_multiclass] = true;
                        }
                        else
                        {
                            menuFlags[allow_multiclass] = false;
                        }
//S: menuFlags[allow_multiclass] should always be true.

to here:
ovr018.cs, line 197:
                        case 'H':
                            if (menuFlags[allow_multiclass] == true)
                            {
                                ovr026.multiclass(gbl.player_ptr);
                            }
                            break;

//S: the condition checking should be made here. e.g.
                        case 'H':
                            if (gbl.area2_ptr.training_class_mask <= 0 && 
                                Cheats.free_training == false)
                            {
                                seg041.DisplayStatusText(0, 14, 
                                    "???");
                            }
                            else if (!ovr026.IsHuman(gbl.player_ptr))
                            {
                                seg041.DisplayStatusText(0, 14, 
                                    "Not Human");
                            }
                            else if
(ovr026.HumanFirstOldClass_Unknown(gbl.player_ptr) != ClassId.unknown)
                            {
                                seg041.DisplayStatusText(0, 14, 
                                    "Already dual-class");
                            }
                            else if (menuFlags[allow_multiclass] == true)
                            {
                                ovr026.multiclass(gbl.player_ptr);
                            }
                            break;

B) is actually the elimination of the "Already dual-class" condition.
As far as I could see it needs to be fixed in 3 places.

B1) the menu itself, like above. If A) is implemented simply remove the
condition.

B2) the recalculation of menu:
ovr018.cs, line 145:
                        bool var_11 = (ovr026.IsHuman(gbl.player_ptr) &&
ovr026.HumanFirstOldClass_Unknown(gbl.player_ptr) == ClassId.unknown);
...
                        if (ovr026.IsHuman(gbl.player_ptr) == false ||

ovr026.HumanFirstOldClass_Unknown(gbl.player_ptr) != ClassId.unknown)

//S: remove old class condition
                        bool var_11 = (ovr026.IsHuman(gbl.player_ptr));
...
                        if (ovr026.IsHuman(gbl.player_ptr) == false)

If A) is implemented the whole section can be ignored: replace lines 145-160:
                        bool var_11 = ...
                        ...
                        reclac_menus = var_11 &&
gbl.area2_ptr.training_class_mask > 0;


with this single line:
                        reclac_menus = gbl.area2_ptr.training_class_mask > 0;

By the way I have a feeling that a cheat condition is missing here so it
should definitely look like:
                        reclac_menus = (gbl.area2_ptr.training_class_mask
<= 0 && Cheats.free_training == false)

B3) Recalculation of HP on CON change:
This is the hardest part.
ovr024.cs, line 1057:
                    byte var_B = player.ClassLevelsOld[var_C];

                    if (var_B > 0)
                    {
                        sub_647BE(var_B, var_C, stat_a, ref var_A, player);
                    }

This is within a for loop, therefore the Con bonus will be calculated and
summed for all old classes. I made a try with:
Fighter 9 / Ranger 11
For Con 19 the bonus is 10x5 = 50
However, your version calculates 90 with Con 19 and 72 with Con 18 (i.e. 18
x bonus, where 18 = 9 + min(9,11)). 
This is definitely a defect and will open a separate issue for it.

Then modified saved game to:
Fighter 9/Paladin 8/Ranger 11/Magic-User 7/Thief 6
The Con bonus should be:
6x2 for Thief
1x2 for Magic-User
1x5 for Paladin
1x5 for Fighter
1x5 for Ranger (no Con bonus on 11th level)
For a total of 29. Instead, I think the bonus is calculated as:
6x2 + 7x2 + 8x5 + 9x5 + 5 (note: it is properly calculated for the current
class) = 116. However, for some reason, the DOS version calculated 121 (I
have no explanation).
Your version calculates 130/156, depending on Con 18/19.

Nevertheless, either the calls to the sub or the sub itself should be
rewritten to be conscious about multiple dual classes.

I'm still thinking about an effective (linear) algorithm like filling a
vector and summing it up at the end.

Original issue reported on code.google.com by surrano on 16 Sep 2009 at 8:10

Thinking a bit about it I would still add two constraints here:
- allow class change only if current level (HitDice?) is greater than largest 
of old
(multiclassLevel?)
- make sure not to allow picking the same class twice.
This latter could be checked easily by checking classLevelsOld[x].

Original comment by surrano on 18 Sep 2009 at 10:47

Implemented all this except CON bonus calculation. There were slight differences
compared to the code snippets above, but the main idea of having a multiclass
character seems to work. Of course, I will test it a lot. :)

Original comment by surrano on 18 Sep 2009 at 1:33

Came up with the following algorithm: (will test next week...)

// create an array for breaking points in hit dice calculation
const int MAXLEVEL = 10; // highest level that can be achieved in game
byte bonus[MAXLEVEL];
minindex = 99; // something impossible
minindex9 = 99;
minindex10 = 99;
for (int i=0; i<MAXLEVEL; i++) bonus[i] = 99; // something impossible

// check max for all classes
for (int oldclass=0; oldclass<7; oldclass++) {
    index = ClassLevelsOld[oldclass];
    byte limit = max_class_levels[oldclass]-1;
    // calculate limit but watch out for things like level 10/11/12 with same
limits... smaller should win.
    if (index > limit) {
       if (limit==9 && minindex9 > index) {
           minindex9 = index;
           index = limit;
       } else if (limit==10 && minindex10 > index) {
           minindex10 = index;
           index = limit;
       } else { // a more relevant value for the given level has already been set
           index = 0;
       }
    }
    // now if there is a relevant level in this particular old class then remember
    // the corresponding HP bonus for that level.
    if (index > 0) {
        //... calculate con bonus based on class and store it in bonus[index-1]
        if (index < minindex) {
            minindex = index;
            // ... store level 1 value (double for ranger) in bonus[0]
        }
    }
}
// sum up
byte sum = 0;
byte delta = 99; // same impossible value as above
for (i = MAXLEVEL; i>0; i--) {
    if (bonus[i-1]<99) delta = bonus[i-1];
    if (delta < 99) sum += delta;
}
return sum;

Original comment by surrano on 19 Sep 2009 at 1:40

Sorry, I was too hasty and wrong. The situation is more complicated. Maybe a 
sort of
all levels cannot be avoided after all.

Original comment by surrano on 19 Sep 2009 at 1:42

The game is 1st Ad, so I'll leave it at single Duel classing. 

I'll re-read the issue in that regard, to see if there are problems that need 
addressing.

Original comment by simeon.pilgrim on 19 Sep 2009 at 7:20

Original comment by simeon.pilgrim on 19 Sep 2009 at 7:22

  • Added labels: Type-Enhancement, Priority-Low
  • Removed labels: Type-Defect, Priority-Medium
I think there is only one defect, the calculation of Max HP for dual class. I
reported it separately in Issue 43.

As for the enhancement: I'll see if I'm able to come up with a neat and tidy 
version
that can be optionally enabled in configuration. In that case, will you consider
including it in the project?

Original comment by surrano on 20 Sep 2009 at 1:19

Two main reasons not to support multiple duel-classing, 
1. The game is 1st edition, there are many other things that are done the 1st 
ed. way.
2. However it's record (I assume you want to save your game) would not be 
compatible
with the DOS version. 

It might make more sense to just add a "all classes" cheat which mean all PCs 
are
every class...

Original comment by simeon.pilgrim on 23 Sep 2009 at 11:11

  • Changed state: WontFix

Original comment by simeon.pilgrim on 28 Jul 2010 at 10:32