[Scummvm-git-logs] scummvm master -> faaf89389bd03ff9d8d820fa853ad7cfa35b3576

sluicebox noreply at scummvm.org
Fri Jan 31 05:18:41 UTC 2025


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:
5c30bfb1b1 AGI: Update misc AGIv1 opcode details
faaf89389b AGI: Implement near.water opcode


Commit: 5c30bfb1b1ac8d2bf036d50cc0d3a2748682557b
    https://github.com/scummvm/scummvm/commit/5c30bfb1b1ac8d2bf036d50cc0d3a2748682557b
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-01-30T21:14:03-08:00

Commit Message:
AGI: Update misc AGIv1 opcode details

Changed paths:
    engines/agi/op_cmd.cpp
    engines/agi/opcodes.cpp


diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index 2bd64b652d7..7319bdb2d16 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -41,16 +41,10 @@ void cmdIncrement(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 	uint16 varNr = parameter[0];
 	byte   varVal = vm->getVar(varNr);
 
-	if (vm->getVersion() < 0x2000) {
-		if (varVal < 0xf0) {
-			varVal++;
-			vm->setVar(varNr, varVal);
-		}
-	} else {
-		if (varVal != 0xff) {
-			varVal++;
-			vm->setVar(varNr, varVal);
-		}
+	byte maxValue = (vm->getVersion() < 0x2000) ? 0xf0 : 0xff;
+	if (varVal < maxValue) {
+		varVal++;
+		vm->setVar(varNr, varVal);
 	}
 }
 
@@ -577,7 +571,9 @@ void cmdNormalCycle(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
 
 	screenObj->cycle = kCycleNormal;
-	screenObj->flags |= fCycling;
+	if (vm->getVersion() >= 0x2000) {
+		screenObj->flags |= fCycling;
+	}
 }
 
 void cmdReverseCycle(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
@@ -585,7 +581,9 @@ void cmdReverseCycle(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];
 
 	screenObj->cycle = kCycleReverse;
-	screenObj->flags |= fCycling;
+	if (vm->getVersion() >= 0x2000) {
+		screenObj->flags |= fCycling;
+	}
 }
 
 void cmdSetDir(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp
index 237b03295f8..a6af4a95dd4 100644
--- a/engines/agi/opcodes.cpp
+++ b/engines/agi/opcodes.cpp
@@ -63,7 +63,7 @@ static const AgiOpCodeDefinitionEntry opCodesV1[] = {
 	{ "subv",               "vv",       &cmdSubV },             // 08
 	{ "load.view",          "n",        &cmdLoadView },         // 09
 	{ "animate.obj",        "n",        &cmdAnimateObj },       // 0A
-	{ "new.room",           "n",        &cmdNewRoom },          // 0B
+	{ "new.room",           "n",        &cmdNewRoom },          // 0B TODO
 	{ "draw.pic",           "v",        &cmdDrawPicV1 },        // 0C
 	{ "print",              "s",        &cmdPrint },            // 0D TODO
 	{ "status",             "",         &cmdStatus },           // 0E TODO
@@ -133,22 +133,22 @@ static const AgiOpCodeDefinitionEntry opCodesV1[] = {
 	{ "show.obj",           "n",        &cmdShowObj },          // 4E # show.obj (KQ2)
 	{ "load.logics",        "n",        &cmdLoadLogic },        // 4F # load.global.logics
 	{ "display",            "nnns",     &cmdDisplay },          // 50 TODO: 4 vs 3 args
-	{ "prevent.input???",   "",         &cmdUnknown },          // 51
-	{ "...",                "",         &cmdUnknown },          // 52 # nop
+	{ "prevent.input",      "",         &cmdUnknown },          // 51 TODO: disables input by clearing a global, reset on new.room
+	{ "...",                "",         &cmdUnknown },          // 52 nop, 0 args
 	{ "text.screen",        "n",        &cmdUnknown },          // 53
 	{ "graphics",           "",         &cmdUnknown },          // 54
 	{ "stop.motion",        "",         &cmdStopMotion },       // 55
 	{ "discard.view",       "n",        &cmdDiscardView },      // 56
 	{ "discard.pic",        "v",        &cmdDiscardPic },       // 57
 	{ "set.item.view",      "nn",       &cmdSetItemView },      // 58
-	{ "...",                "",         &cmdUnknown },          // 59 # reverse.cycle, unused in KQ2 or BC
+	{ "reverse.cycle",      "n",        &cmdReverseCycle },     // 59
 	{ "last.cel",           "nv",       &cmdLastCel },          // 5A
 	{ "set.cel.v",          "nv",       &cmdSetCelF },          // 5B
-	{ "...",                "",         &cmdUnknown },          // 5C # normal.cycle, unused in KQ2 or BC
-	{ "load.view",          "n",        &cmdLoadView },         // 5D
-	{ "...",                "",         &cmdUnknown },          // 5E unused in KQ2 or BC
+	{ "normal.cycle",       "n",        &cmdNormalCycle },      // 5C
+	{ "load.view",          "n",        &cmdLoadView },         // 5D duplicate opcode: same table entry as 09
+	{ "...",                "n",        &cmdUnknown },          // 5E nop, 1 arg
 	{ "...",                "",         &cmdUnknown },          // 5F BC script 102 when attempting to fill flask
-	{ "set.bit",            "nv",       &cmdSetBit },           // 60 BC
+	{ "set.bit",            "nv",       &cmdSetBit },           // 60
 	{ "clear.bit",          "nv",       &cmdClearBit },         // 61
 	{ "set.upper.left",     "nn",       &cmdSetUpperLeft }      // 62 BC Apple II
 };


Commit: faaf89389bd03ff9d8d820fa853ad7cfa35b3576
    https://github.com/scummvm/scummvm/commit/faaf89389bd03ff9d8d820fa853ad7cfa35b3576
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-01-30T21:14:57-08:00

Commit Message:
AGI: Implement near.water opcode

Used by Black Cauldron AGIv1 when filling flask

Changed paths:
    engines/agi/agi.h
    engines/agi/checks.cpp
    engines/agi/op_cmd.cpp
    engines/agi/opcodes.cpp
    engines/agi/opcodes.h


diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index a46f38d18d0..b003b839966 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -900,6 +900,8 @@ public:
 	void fixPosition(ScreenObjEntry *screenObj);
 	void updatePosition();
 	int getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize);
+	byte egoNearWater(byte limit);
+	int16 nearWater(ScreenObjEntry &screenObj, byte direction, int16 x, int16 y, byte limit);
 
 	bool _keyHoldMode;
 	Common::KeyCode _keyHoldModeLastKey;
diff --git a/engines/agi/checks.cpp b/engines/agi/checks.cpp
index 31a8904becc..639b929728c 100644
--- a/engines/agi/checks.cpp
+++ b/engines/agi/checks.cpp
@@ -351,4 +351,145 @@ void AgiEngine::fixPosition(ScreenObjEntry *screenObj) {
 	debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos);
 }
 
+/**
+ * Tests if ego is facing nearby water without obstacles. Used by opcode 5F in
+ * Black Cauldron AGIv1 to test if the water flask can be filled. Removed from
+ * the interpreter in AGIv2 and replaced with position tests in game scripts.
+ *
+ * Returns distance to water or 250 if water is not found or is blocked.
+ */
+byte AgiEngine::egoNearWater(byte limit) {
+	ScreenObjEntry &ego = _game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];
+	int16 x1 = ego.xPos;
+	int16 x2 = 0;
+	byte direction;
+
+	switch (ego.currentLoopNr) {
+	case 0: // right
+		direction = 3;
+		x1 += ego.xSize;
+		break;
+	case 1: // left
+		direction = 7;
+		break;
+	case 2: // down
+		direction = 5;
+		x1 += (ego.xSize / 2);
+		break;
+	case 3: // up
+		direction = 1;
+		x2 = x1 + ego.xSize;
+		x1--;
+		break;
+	default: // unhandled in original
+		return 250; // no water
+	}
+
+	int16 distance = -1; // uninitialized in original
+	while (x1 != 0) {
+		distance = nearWater(ego, direction, x1, ego.yPos, limit);
+		if (distance != -1) {
+			break;
+		}
+		x1 = x2;
+		x2 = 0;
+	}
+
+	if (distance == -1) {
+		return 250; // no water
+	}
+
+	// adjust ego positions for collision check
+	int16 prevPrevX = ego.xPos_prev;
+	int16 prevPrevY = ego.yPos_prev;
+	ego.xPos_prev = ego.xPos;
+	ego.yPos_prev = ego.yPos;
+	switch (direction) {
+	case 1: // up
+		ego.yPos -= distance;
+		break;
+	case 3: // right
+		ego.xPos += (x1 - ego.xSize);
+		break;
+	case 5: // down
+		ego.yPos += distance;
+		break;
+	case 7: // left
+		ego.xPos -= distance;
+		break;
+	default:
+		break;
+	}
+
+	if (!checkCollision(&ego)) {
+		if (_game.block.active) {
+			if (!(ego.flags & fIgnoreBlocks)) {
+				if (checkBlock(ego.xPos, ego.yPos)) {
+					distance = 250; // no water
+				}
+			}
+		}
+	} else {
+		distance = 250; // no water
+	}
+
+	// restore ego positions
+	ego.xPos = ego.xPos_prev;
+	ego.yPos = ego.yPos_prev;
+	ego.xPos_prev = prevPrevX;
+	ego.yPos_prev = prevPrevY;
+
+	return distance;
+}
+
+/**
+ * Tests if a screen object is near water in a given direction.
+ *
+ * Returns the distance to water or -1 if water is not found or is blocked.
+ *
+ * Note that the original contains a bug that scans left when facing right.
+ * We do not implement this bug. In Black Cauldron AGIv1, it prevents filling
+ * the flask when facing right unless ego is on or facing away from water.
+ */
+int16 AgiEngine::nearWater(ScreenObjEntry &screenObj, byte direction, int16 x, int16 y, byte limit) {
+	int16 dx = 0;
+	int16 dy = 0;
+	switch (direction) {
+	case 1: dy = -1; break;
+	case 3: dx =  1; break;
+	case 5: dy =  1; break;
+	case 7: dx = -1; break;
+	default: break;
+	}
+
+	for (int16 i = 0; i <= limit; i++) {
+		if (!(0 <= x && x < SCRIPT_WIDTH && 0 <= y && y < SCRIPT_HEIGHT)) {
+			break;
+		}
+
+		byte priority = _gfx->getPriority(x, y);
+		x += dx;
+		y += dy;
+
+		// water found?
+		if (priority == 3) {
+			return i;
+		}
+
+		if (screenObj.priority != 15) {
+			// is blocked by priority 0?
+			if (priority == 0) {
+				break;
+			}
+
+			// is blocked by priority 1?
+			if (priority == 1 && !(screenObj.flags & fIgnoreBlocks)) {
+				break;
+			}
+		}
+	}
+
+	return -1; // water not found
+}
+
 } // End of namespace Agi
diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index 7319bdb2d16..2f918270495 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -2297,6 +2297,14 @@ void cmdNewRoomVV1(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 	vm->setVar(13, 1);
 }
 
+void cmdNearWater(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
+	byte limit = parameter[0];
+	byte varNr = parameter[1];
+
+	byte distance = vm->egoNearWater(limit);
+	vm->setVar(varNr, distance);
+}
+
 void cmdSetBit(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 	uint16 bit = parameter[0];
 	uint16 varNr = parameter[1];
diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp
index a6af4a95dd4..676ce925b2d 100644
--- a/engines/agi/opcodes.cpp
+++ b/engines/agi/opcodes.cpp
@@ -147,7 +147,7 @@ static const AgiOpCodeDefinitionEntry opCodesV1[] = {
 	{ "normal.cycle",       "n",        &cmdNormalCycle },      // 5C
 	{ "load.view",          "n",        &cmdLoadView },         // 5D duplicate opcode: same table entry as 09
 	{ "...",                "n",        &cmdUnknown },          // 5E nop, 1 arg
-	{ "...",                "",         &cmdUnknown },          // 5F BC script 102 when attempting to fill flask
+	{ "near.water",         "nv",       &cmdNearWater },        // 5F BC script 102 when attempting to fill flask
 	{ "set.bit",            "nv",       &cmdSetBit },           // 60
 	{ "clear.bit",          "nv",       &cmdClearBit },         // 61
 	{ "set.upper.left",     "nn",       &cmdSetUpperLeft }      // 62 BC Apple II
diff --git a/engines/agi/opcodes.h b/engines/agi/opcodes.h
index 26bf778eb49..fb0bd9f0e89 100644
--- a/engines/agi/opcodes.h
+++ b/engines/agi/opcodes.h
@@ -225,6 +225,7 @@ void cmdAdjEgoMoveToXY(AgiGame *state, AgiEngine *vm, uint8 *p);
 void cmdSetSpeed(AgiGame *state, AgiEngine *vm, uint8 *p);
 void cmdSetItemView(AgiGame *state, AgiEngine *vm, uint8 *p);
 void cmdCallV1(AgiGame *state, AgiEngine *vm, uint8 *p);
+void cmdNearWater(AgiGame *state, AgiEngine *vm, uint8 *p);
 void cmdSetBit(AgiGame *state, AgiEngine *vm, uint8 *p);
 void cmdClearBit(AgiGame *state, AgiEngine *vm, uint8 *p);
 void cmdUnknown(AgiGame *state, AgiEngine *vm, uint8 *p);




More information about the Scummvm-git-logs mailing list