[Scummvm-git-logs] scummvm master -> 4d432731970eba70b9a051c5f6afe651c82e4568

athrxx noreply at scummvm.org
Tue Jul 23 21:48:04 UTC 2024


This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
46c194001d KYRA: (EOB) - Hit Point rule enahncements
4d43273197 KYRA: (EOB) - More Hit Point code refactoring


Commit: 46c194001dcb0072fd7657f2c5fb513a35415384
    https://github.com/scummvm/scummvm/commit/46c194001dcb0072fd7657f2c5fb513a35415384
Author: Vladimir Vrzić (vvrzic at gmail.com)
Date: 2024-07-23T23:48:00+02:00

Commit Message:
KYRA: (EOB) - Hit Point rule enahncements

For accurate HP calculation, we track the HP dividend.
When leveling up, we divide the dividend by the number
of character subclasses to get the total Hit Points.

- Added field hitPointsDividend
- Cleaned up and refactored HP calculation code
- Extracted method rollHitDie
- Updated character generation to use enhanced rules

Changed paths:
    engines/kyra/engine/chargen.cpp
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/eobcommon.h
    engines/kyra/gui/saveload.cpp
    engines/kyra/gui/saveload_eob.cpp


diff --git a/engines/kyra/engine/chargen.cpp b/engines/kyra/engine/chargen.cpp
index 880aa53dc38..5adf310ddda 100644
--- a/engines/kyra/engine/chargen.cpp
+++ b/engines/kyra/engine/chargen.cpp
@@ -164,7 +164,7 @@ CharacterGenerator::CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen) :
 	memset(_chargenMaxStats, 0, sizeof(_chargenMaxStats));
 	memset(_chargenButtonLabels, 0, sizeof(_chargenButtonLabels));
 	memset(_nameLabelsZH, 0, sizeof(_nameLabelsZH));
-	
+
 	int temp;
 	_chargenStrings1 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings1, temp);
 	_chargenStrings2 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings2, temp);
@@ -860,7 +860,7 @@ int CharacterGenerator::classMenu(int raceSex) {
 					      _chargenButtonDefs[41].x + _chargenButtonDefs[41].w, _chargenButtonDefs[41].y + _chargenButtonDefs[41].h)) {
 			if (in == 199 || in == 201) {
 				res = _vm->_keyMap[Common::KEYCODE_ESCAPE];
-			} else { 
+			} else {
 				if (_vm->_flags.lang == Common::ZH_TWN && !backBtnHiLite) {
 					drawButton(5, 1);
 					_vm->_gui->simpleMenu_unselect(2, _chargenClassStrings, 0, itemsMask, 0);
@@ -1038,10 +1038,18 @@ void CharacterGenerator::generateStats(int index) {
 	c->charismaCur = c->charismaMax = sv[5] & 0xFF;
 	c->armorClass = 10 + _vm->getDexterityArmorClassModifier(sv[3] & 0xFF);
 	c->hitPointsCur = 0;
+	c->hitPointsDividend = 0;
 
 	for (int l = 0; l < 3; l++) {
-		for (int i = 0; i < c->level[l]; i++)
-			c->hitPointsCur += _vm->generateCharacterHitpointsByLevel(index, 1 << l);
+		for (int i = 0; i < c->level[l]; i++) {
+			int hitDieRoll = _vm->rollHitDie(index, l);
+			c->hitPointsDividend += _vm->incrCharacterHitPointsDividendByLevel(index, l, hitDieRoll);
+			if (_vm->_configADDRuleEnhancements) {
+				c->hitPointsCur = c->hitPointsDividend / _vm->_numLevelsPerClass[c->cClass];
+			} else {
+				c->hitPointsCur += _vm->generateCharacterHitpointsByLevel(index, l, hitDieRoll);
+			}
+		}
 	}
 
 	c->hitPointsMax = c->hitPointsCur;
@@ -1363,7 +1371,7 @@ void CharacterGenerator::processNameInput(int index, int textColor) {
 	} else {
 		_screen->fillRect(_chargenNameFieldX[index], _chargenNameFieldY[index], _chargenNameFieldX[index] + 59, _chargenNameFieldY[index] + 5, _vm->guiSettings()->colors.guiColorBlack);
 		_screen->printText(_characters[index].name, _chargenNameFieldX[index] + ((60 - _screen->getTextWidth(_characters[index].name)) >> 1), _chargenNameFieldY[index], textColor, 0);
-	}	
+	}
 	_screen->updateScreen();
 	_screen->setFont(of);
 }
@@ -1481,8 +1489,10 @@ int CharacterGenerator::modifyStat(int index, int8 *stat1, int8 *stat2) {
 
 		*s1 = v1;
 
-		if (index == 6)
+		if (index == 6) {
 			_characters[_activeBox].hitPointsMax = v1;
+			_characters[_activeBox].hitPointsDividend = c->hitPointsMax * _vm->_numLevelsPerClass[c->cClass];
+		}
 
 		bool hpChanged = false;
 		bool acChanged = false;
@@ -2156,7 +2166,7 @@ bool TransferPartyWiz::selectAndLoadTransferFile() {
 	Common::String target = _vm->_gui->transferTargetMenu(eobTargets);
 	_screen->clearPage(0);
 	_screen->copyPage(12, 0);
-	
+
 	if (target.empty())
 		return true;
 
diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index e233a2b8cb3..967c11c2328 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -598,7 +598,7 @@ void EoBCoreEngine::loadFonts() {
 			_screen->loadFont(Screen::FID_SJIS_SMALL_FNT, "FONT12.FNT");
 			_bookFont = Screen::FID_SJIS_SMALL_FNT;
 			_invFont4 = _invFont5 = _invFont6 = Screen::FID_SJIS_FNT;
-		}		
+		}
 		_titleFont = _conFont = _invFont3 = Screen::FID_SJIS_FNT;
 		_invFont1 = Screen::FID_SJIS_SMALL_FNT;
 	} else if (_flags.platform == Common::kPlatformSegaCD) {
@@ -850,7 +850,7 @@ void EoBCoreEngine::loadItemsAndDecorationsShapes() {
 		}
 	}
 
-	_thrownItemShapes = new const uint8*[_numThrownItemShapes];	
+	_thrownItemShapes = new const uint8*[_numThrownItemShapes];
 	_firebeamShapes = new const uint8*[3];
 
 	_screen->loadShapeSetBitmap("THROWN", 5, 3);
@@ -892,7 +892,7 @@ void EoBCoreEngine::loadItemsAndDecorationsShapes() {
 
 	_teleporterShapes = new const uint8*[6];
 	_sparkShapes = new const uint8*[4];
-	_compassShapes = new const uint8*[12];		
+	_compassShapes = new const uint8*[12];
 
 	_screen->loadShapeSetBitmap("DECORATE", 5, 3);
 	if (_flags.gameID == GI_EOB2) {
@@ -1032,39 +1032,62 @@ int EoBCoreEngine::getDexterityArmorClassModifier(int dexterity) {
 	return mod[dexterity];
 }
 
-int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex) {
+int EoBCoreEngine::rollHitDie(int charIndex, int levelIndex) {
 	EoBCharacter *c = &_characters[charIndex];
-	int m = getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur);
+	int classOffset = getCharacterClassType(c->cClass, levelIndex);
+	int die = classOffset >= 0 ? _hpIncrPerLevel[classOffset] : 1;
+	int roll = rollDice(1, die);
+	return roll;
+}
 
-	int h = 0;
+int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex, int hitDieRoll) {
+	const unsigned int kOffsetHPRollMaxLevel = 6;
+	const unsigned int kOffsetStaticHPBonus = 12;
 
-	for (int i = 0; i < 3; i++) {
-		if (!(levelIndex & (1 << i)))
-			continue;
+	EoBCharacter *c = &_characters[charIndex];
+	int d = getCharacterClassType(c->cClass, levelIndex);
 
-		int d = getCharacterClassType(c->cClass, i);
+	int hp = 0;
 
-		if (c->level[i] <= (d >= 0 ? _hpIncrPerLevel[6 + d] : 0)) {
-			int hpAdjustment = m;
-			hpAdjustment += rollDice(1, (d >= 0) ? _hpIncrPerLevel[d] : 0);
-			// According to the AD&D handbook the const bonus shouldn't be added here
-			// when the dice roll is 0, but it is like that in the original interpreter.
-			h += (hpAdjustment < 1 && _configADDRuleEnhancements) ? 1 : hpAdjustment;
-		} else {
-			h += (d >= 0 ? _hpIncrPerLevel[12 + d] : 0);
-			// The const bonus shouldn't be added here according to
-			// the AD&D handbook, but the game does it anyway.
-			if (!_configADDRuleEnhancements)
-				h += m;
-		}
-	}
+	if (c->level[levelIndex] <= (d >= 0 ? _hpIncrPerLevel[kOffsetHPRollMaxLevel + d] : 0))
+		hp += hitDieRoll;
+	else
+		hp += d >= 0 ? _hpIncrPerLevel[kOffsetStaticHPBonus + d] : 0;
+
+	hp += getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur);
+
+	hp /= _numLevelsPerClass[c->cClass];
+
+	if (hp < 1)
+		hp = 1;
 
-	h /= _numLevelsPerClass[c->cClass];
+	return hp;
+}
 
-	if (h < 1)
-		h = 1;
+int EoBCoreEngine::incrCharacterHitPointsDividendByLevel(int charIndex, int levelIndex, int hitDieRoll) {
+	const unsigned int kOffsetHPRollMaxLevel = 6;
+	const unsigned int kOffsetStaticHPBonus = 12;
 
-	return h;
+	EoBCharacter *c = &_characters[charIndex];
+	int d = getCharacterClassType(c->cClass, levelIndex);
+
+	if (c->level[levelIndex] <= (d >= 0 ? _hpIncrPerLevel[kOffsetHPRollMaxLevel + d] : 0)) {
+		/*
+		 * Per AD&D 2nd Edition Player's Handbook, HP adjustment is added
+		 * or substracted from each Hit Die rolled for the character.
+		 *
+		 * If the adjustment would lower the number rolled to 0 or less,
+		 * the final result should be considered to be 1.
+		 *
+		 * The original game adds the HP adjustment even when Hit Die
+		 * is not rolled.
+		 */
+		int hpAdjustment = getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur);
+		int hp = hitDieRoll + hpAdjustment;
+		return hp < 1 ? 1 : hp;
+	} else {
+		return d >= 0 ? _hpIncrPerLevel[kOffsetStaticHPBonus + d] : 0;
+	}
 }
 
 int EoBCoreEngine::getClassAndConstHitpointsModifier(int cclass, int constitution) {
@@ -1544,10 +1567,34 @@ uint32 EoBCoreEngine::getRequiredExperience(int cClass, int levelIndex, int leve
 }
 
 void EoBCoreEngine::increaseCharacterLevel(int charIndex, int levelIndex) {
+	uint8 numSubclasses = _numLevelsPerClass[(_characters[charIndex].cClass)];
+
+	if (_characters[charIndex].hitPointsDividend == 0) {
+		_characters[charIndex].hitPointsDividend = _characters[charIndex].hitPointsMax * numSubclasses;
+	}
+
+	int hitDieRoll = rollHitDie(charIndex, levelIndex);
+
+	_characters[charIndex].hitPointsDividend += incrCharacterHitPointsDividendByLevel(charIndex, levelIndex, hitDieRoll);
+
+	if (_configADDRuleEnhancements) {
+		/*
+		 * The original game introduces a rounding error when
+		 * calculating HP gains for multi-class characters.
+		 * To avoid accumulating such round-off errors, we
+		 * store the dividend of the total Hit Points, and
+		 * recalculate Hit Points from it on every level up.
+		 */
+		int hpNew = _characters[charIndex].hitPointsDividend / numSubclasses;
+		_characters[charIndex].hitPointsCur += hpNew - _characters[charIndex].hitPointsMax;
+		_characters[charIndex].hitPointsMax = hpNew;
+	} else {
+		int hpInc = generateCharacterHitpointsByLevel(charIndex, levelIndex, hitDieRoll);
+		_characters[charIndex].hitPointsCur += hpInc;
+		_characters[charIndex].hitPointsMax += hpInc;
+	}
+
 	_characters[charIndex].level[levelIndex]++;
-	int hpInc = generateCharacterHitpointsByLevel(charIndex, 1 << levelIndex);
-	_characters[charIndex].hitPointsCur += hpInc;
-	_characters[charIndex].hitPointsMax += hpInc;
 
 	gui_drawCharPortraitWithStats(charIndex);
 	_txt->printMessage(_levelGainStrings[0], -1, _characters[charIndex].name);
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index 526b9ca9fae..a1a0eb49fa5 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -116,6 +116,7 @@ struct EoBCharacter {
 	uint8 food;
 	uint8 level[3];
 	uint32 experience[3];
+	int16 hitPointsDividend;
 	const uint8 *faceShape;
 	const uint8 *nameShape;
 
@@ -397,7 +398,9 @@ protected:
 
 	// Characters
 	int getDexterityArmorClassModifier(int dexterity);
-	int generateCharacterHitpointsByLevel(int charIndex, int levelIndex);
+	int rollHitDie(int charIndex, int levelIndex);
+	int generateCharacterHitpointsByLevel(int charIndex, int levelIndex, int hitDieRoll);
+	int incrCharacterHitPointsDividendByLevel(int charIndex, int levelIndex, int hitDieRoll);
 	int getClassAndConstHitpointsModifier(int cclass, int constitution);
 	int getCharacterClassType(int cclass, int levelIndex);
 	int getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode);
diff --git a/engines/kyra/gui/saveload.cpp b/engines/kyra/gui/saveload.cpp
index b7aa18fd89d..19907ea3074 100644
--- a/engines/kyra/gui/saveload.cpp
+++ b/engines/kyra/gui/saveload.cpp
@@ -28,7 +28,7 @@
 #include "graphics/thumbnail.h"
 #include "graphics/surface.h"
 
-#define CURRENT_SAVE_VERSION 21
+#define CURRENT_SAVE_VERSION 22
 
 #define GF_FLOPPY  (1 <<  0)
 #define GF_TALKIE  (1 <<  1)
diff --git a/engines/kyra/gui/saveload_eob.cpp b/engines/kyra/gui/saveload_eob.cpp
index 2b8c4201c95..868d3ad4634 100644
--- a/engines/kyra/gui/saveload_eob.cpp
+++ b/engines/kyra/gui/saveload_eob.cpp
@@ -85,6 +85,8 @@ Common::Error EoBCoreEngine::loadGameState(int slot) {
 		in.read(c->level, 3);
 		for (int ii = 0; ii < 3; ii++)
 			c->experience[ii] = in.readUint32BE();
+		if (header.version >= 22)
+			c->hitPointsDividend = in.readSint16BE();
 		delete[] c->faceShape;
 		c->faceShape = 0;
 		in.read(c->mageSpells, 80);
@@ -381,6 +383,7 @@ Common::Error EoBCoreEngine::saveGameStateIntern(int slot, const char *saveName,
 		out->write(c->level, 3);
 		for (int ii = 0; ii < 3; ii++)
 			out->writeUint32BE(c->experience[ii]);
+		out->writeSint16BE(c->hitPointsDividend);
 		out->write(c->mageSpells, 80);
 		out->write(c->clericSpells, 80);
 		out->writeUint32BE(c->mageSpellsAvailableFlags);


Commit: 4d432731970eba70b9a051c5f6afe651c82e4568
    https://github.com/scummvm/scummvm/commit/4d432731970eba70b9a051c5f6afe651c82e4568
Author: Vladimir Vrzić (vvrzic at gmail.com)
Date: 2024-07-23T23:48:00+02:00

Commit Message:
KYRA: (EOB) - More Hit Point code refactoring

- extracted method shouldRollHitDieAtCurrentLevel
- extacted method getStaticHitPointBonus.

Changed paths:
    engines/kyra/engine/eobcommon.cpp
    engines/kyra/engine/eobcommon.h


diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp
index 967c11c2328..340868605f9 100644
--- a/engines/kyra/engine/eobcommon.cpp
+++ b/engines/kyra/engine/eobcommon.cpp
@@ -1040,19 +1040,29 @@ int EoBCoreEngine::rollHitDie(int charIndex, int levelIndex) {
 	return roll;
 }
 
-int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex, int hitDieRoll) {
+bool EoBCoreEngine::shouldRollHitDieAtCurrentLevel(int charIndex, int levelIndex) {
 	const unsigned int kOffsetHPRollMaxLevel = 6;
-	const unsigned int kOffsetStaticHPBonus = 12;
+	EoBCharacter *c = &_characters[charIndex];
+	int d = getCharacterClassType(c->cClass, levelIndex);
+	return c->level[levelIndex] <= (d >= 0 ? _hpIncrPerLevel[kOffsetHPRollMaxLevel + d] : 0);
+}
 
+uint8 EoBCoreEngine::getStaticHitPointBonus(int charIndex, int levelIndex) {
+	const unsigned int kOffsetStaticHPBonus = 12;
 	EoBCharacter *c = &_characters[charIndex];
 	int d = getCharacterClassType(c->cClass, levelIndex);
+	return d >= 0 ? _hpIncrPerLevel[kOffsetStaticHPBonus + d] : 0;
+}
+
+int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex, int hitDieRoll) {
+	EoBCharacter *c = &_characters[charIndex];
 
 	int hp = 0;
 
-	if (c->level[levelIndex] <= (d >= 0 ? _hpIncrPerLevel[kOffsetHPRollMaxLevel + d] : 0))
+	if (shouldRollHitDieAtCurrentLevel(charIndex, levelIndex))
 		hp += hitDieRoll;
 	else
-		hp += d >= 0 ? _hpIncrPerLevel[kOffsetStaticHPBonus + d] : 0;
+		hp += getStaticHitPointBonus(charIndex, levelIndex);
 
 	hp += getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur);
 
@@ -1065,13 +1075,9 @@ int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelInd
 }
 
 int EoBCoreEngine::incrCharacterHitPointsDividendByLevel(int charIndex, int levelIndex, int hitDieRoll) {
-	const unsigned int kOffsetHPRollMaxLevel = 6;
-	const unsigned int kOffsetStaticHPBonus = 12;
-
 	EoBCharacter *c = &_characters[charIndex];
-	int d = getCharacterClassType(c->cClass, levelIndex);
 
-	if (c->level[levelIndex] <= (d >= 0 ? _hpIncrPerLevel[kOffsetHPRollMaxLevel + d] : 0)) {
+	if (shouldRollHitDieAtCurrentLevel(charIndex, levelIndex)) {
 		/*
 		 * Per AD&D 2nd Edition Player's Handbook, HP adjustment is added
 		 * or substracted from each Hit Die rolled for the character.
@@ -1086,7 +1092,7 @@ int EoBCoreEngine::incrCharacterHitPointsDividendByLevel(int charIndex, int leve
 		int hp = hitDieRoll + hpAdjustment;
 		return hp < 1 ? 1 : hp;
 	} else {
-		return d >= 0 ? _hpIncrPerLevel[kOffsetStaticHPBonus + d] : 0;
+		return (int)getStaticHitPointBonus(charIndex, levelIndex);
 	}
 }
 
@@ -1573,7 +1579,9 @@ void EoBCoreEngine::increaseCharacterLevel(int charIndex, int levelIndex) {
 		_characters[charIndex].hitPointsDividend = _characters[charIndex].hitPointsMax * numSubclasses;
 	}
 
-	int hitDieRoll = rollHitDie(charIndex, levelIndex);
+	_characters[charIndex].level[levelIndex]++;
+
+	int hitDieRoll = shouldRollHitDieAtCurrentLevel(charIndex, levelIndex) ? rollHitDie(charIndex, levelIndex) : 0;
 
 	_characters[charIndex].hitPointsDividend += incrCharacterHitPointsDividendByLevel(charIndex, levelIndex, hitDieRoll);
 
@@ -1594,8 +1602,6 @@ void EoBCoreEngine::increaseCharacterLevel(int charIndex, int levelIndex) {
 		_characters[charIndex].hitPointsMax += hpInc;
 	}
 
-	_characters[charIndex].level[levelIndex]++;
-
 	gui_drawCharPortraitWithStats(charIndex);
 	_txt->printMessage(_levelGainStrings[0], -1, _characters[charIndex].name);
 	snd_playSoundEffect(_flags.platform == Common::kPlatformSegaCD ? 0x1017 : 0x17);
diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h
index a1a0eb49fa5..611fad43fa9 100644
--- a/engines/kyra/engine/eobcommon.h
+++ b/engines/kyra/engine/eobcommon.h
@@ -399,6 +399,8 @@ protected:
 	// Characters
 	int getDexterityArmorClassModifier(int dexterity);
 	int rollHitDie(int charIndex, int levelIndex);
+	bool shouldRollHitDieAtCurrentLevel(int charIndex, int levelIndex);
+	uint8 getStaticHitPointBonus(int charIndex, int levelIndex);
 	int generateCharacterHitpointsByLevel(int charIndex, int levelIndex, int hitDieRoll);
 	int incrCharacterHitPointsDividendByLevel(int charIndex, int levelIndex, int hitDieRoll);
 	int getClassAndConstHitpointsModifier(int cclass, int constitution);




More information about the Scummvm-git-logs mailing list