/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "access/martian/martian_duct.h" #include "access/martian/martian_game.h" #include "access/martian/martian_resources.h" namespace Access { namespace Martian { MartianDuct::MartianDuct(MartianEngine *vm) : _vm(vm), _stopMoveLoop(false), _playerX(0), _preYOffset(0), _playerY(0), _moveAngle(kMoveAngleNorth), _drawDistX(100), _drawDistY(500), _threshold1(50), _crawlFrame(0), _xOffset(160), _yOffset(160), _xScale(100), _yScale(160), _moveIntent(kMoveIntentNone), _primArrayIdx(0), _mapLoc(0), _nextPlayerX(0), _nextPlayerY(0) { ARRAYCLEAR(_matrix[0]); ARRAYCLEAR(_matrix[1]); ARRAYCLEAR(_matrix[2]); ARRAYCLEAR(_tempPoints); } MartianDuct::~MartianDuct() { } void MartianDuct::duct2() { _playerX = 550; _preYOffset = 10; _playerY = 850; doDuct(); } void MartianDuct::duct4() { _playerX = 2750; _preYOffset = 10; _playerY = 1050; doDuct(); } void MartianDuct::doDuct() { _vm->_screen->forceFadeOut(); _vm->_screen->_windowXAdd = 0; _vm->_screen->_windowYAdd = 0; _vm->_screen->_screenYOff = 0; _vm->_events->hideCursor(); _vm->_files->loadScreen(20, 0); Resource *res = _vm->_files->loadFile(20, 1); _vm->_objectsTable[20] = new SpriteResource(_vm, res); if (_vm->_inventory->_inv[40]._value == ITEM_IN_INVENTORY) { // Show map if we have it. _vm->_screen->plotImage(_vm->_objectsTable[20], 8, Common::Point(140, 10)); } _vm->_events->showCursor(); _vm->_screen->forceFadeIn(); _crawlFrame = 0; _xOffset = 160; _yOffset = 100; _xScale = 160; _yScale = 100; // The game initialises some drawing window here but it makes no difference _moveAngle = kMoveAngleNorth; // game has Y and Z angles X fraction part which is always 0 _drawDistX = 100; _drawDistY = 500; _threshold1 = 50; drawArrowSprites(); drawArrowSprites2(); _vm->_room->_function = FN_NONE; // FIXME: Quick HACK: skip this part //g_system->displayMessageOnOSD(Common::U32String("Duct section not implemented yet!")); //_vm->_flags[0x62] = 1; //_vm->_flags[0x55] = 1; //_vm->_room->_function = FN_CLEAR1; // END HACK while (!_vm->shouldQuit()) { clearWorkScreenArea(); updateMatrix(); applyMatrixToMapData(); // Draw duct panels updatePrimsAndDraw(); // Draw tex over the top _vm->_buffer2.plotImage(_vm->_objectsTable[20], _crawlFrame, Common::Point(140, 94)); copyBufBlockToScreen(); if (_vm->_room->_function != FN_NONE) break; do { waitForMoveUpdate(); } while (!_stopMoveLoop && !_vm->shouldQuitOrRestart()); } delete _vm->_objectsTable[20]; _vm->_objectsTable[20] = nullptr; } void MartianDuct::drawArrowSprites() { int x; int y; int frame; _vm->_events->hideCursor(); _vm->_screen->plotImage(_vm->_objectsTable[20], 7, Common::Point(4, 0x50)); if (_moveAngle == kMoveAngleNorth) { // (highlight up arrow) x = 0x11; y = 0x50; frame = 9; } else if (_moveAngle == kMoveAngleEast) { // highlight right arrow x = 0x19; y = 0x57; frame = 12; } else if (_moveAngle == kMoveAngleSouth) { // highlight down arrow x = 0xe; y = 0x5d; frame = 11; } else { // West or west2 (left arrow) x = 4; y = 0x57; frame = 10; } _vm->_screen->plotImage(_vm->_objectsTable[20], frame, Common::Point(x, y)); _vm->_events->showCursor(); } void MartianDuct::drawArrowSprites2() { _vm->_events->hideCursor(); _vm->_screen->plotImage(_vm->_objectsTable[20], 14, Common::Point(16, 0x58)); if (_mapLoc > 3 && _mapLoc < 13) { _vm->_screen->plotImage(_vm->_objectsTable[20], 13, Common::Point(17, 0x59)); } _vm->_events->showCursor(); } void MartianDuct::clearWorkScreenArea() { _vm->_buffer2.fillRect(Common::Rect(100, 60, 220, 140), 0); // For testing, clear the whole buffer: //_vm->_buffer2.fillRect(Common::Rect(320, 200), 0); } void MartianDuct::copyBufBlockToScreen() { // Start from row 60, 100 across. 100 px by 80 rows. _vm->_screen->copyBlock(&_vm->_buffer2, Common::Rect(100, 60, 220, 140)); // For testing, copy everything: //_vm->_screen->copyBuffer(&_vm->_buffer2); } void MartianDuct::updateMatrix() { // The original does a full fixed-point matrix calculation here, but // half the values are always 1 or 0 so it's much simpler than that. float moveAngleRad = (float)(_moveAngle / 256.0f) * 2.0f * M_PI; float cosVal = cos(moveAngleRad); float sinVal = sin(moveAngleRad); // 3D rotation through Y axis. We never rotate through the others. _matrix[0][0] = cosVal; _matrix[0][1] = 0.0f; _matrix[0][2] = sinVal; _matrix[1][0] = 0.0f; _matrix[1][1] = 1.0f; _matrix[1][2] = 0.0f; _matrix[2][0] = -sinVal; _matrix[2][1] = 0.0f; _matrix[2][2] = cosVal; } void MartianDuct::applyMatrixToMapData() { int16 shapeDataIndex; _renderShapes.clear(); _renderPoints.clear(); for (const DuctMapPoint *pMapData = DUCT_MAP_DATA; shapeDataIndex = pMapData->shapeType, shapeDataIndex != -1; pMapData++) { // The original sets the actual drawing x/y here but then uses them // as inputs to the next step so we don't need to set them here. const int16 blockX = pMapData->x; const int16 blockY = pMapData->y; if (abs(blockX + _threshold1 - _playerX) < _drawDistX && abs(blockY + _threshold1 - _playerY) < _drawDistY) { const DuctShape *ptr = DUCT_SHAPE_DATA[shapeDataIndex]; const uint startPointNum = _renderPoints.size(); const Point3 *pPoint = ptr->points; // The data is the point list followed by the other data. for (int i = 0; i < ptr->numPts; i++) { int16 x = pPoint->x + blockX - _playerX; int16 y = pPoint->y - _preYOffset; int16 z = pPoint->z + blockY - _playerY; pPoint++; doMatrixMulAndAddPoint(x, y, z); } const uint16 *dataPtr = ptr->data; for (int j = 0; j < ptr->array2Len; j++) { RenderShape shapeData; // Input struct is byte,ignored,int16,[array of int16] shapeData._col = (byte)dataPtr[0]; const int dataCount = dataPtr[1]; dataPtr += 2; for (int i = 0; i < dataCount; i++) { shapeData._pointIdxs.push_back(*dataPtr + startPointNum); dataPtr++; } _renderShapes.push_back(shapeData); } } } } void MartianDuct::updatePrimsAndDraw() { int16 shapeZDepth[256]; int shapeIndexes[256]; ARRAYCLEAR(shapeIndexes, -1); ARRAYCLEAR(shapeZDepth, (int16)INT16_MIN); for (uint shpNum = 0; shpNum < _renderShapes.size(); shpNum++) { const Point3 &pt0 = _renderPoints[_renderShapes[shpNum]._pointIdxs[0]]; const Point3 &pt2 = _renderPoints[_renderShapes[shpNum]._pointIdxs[2]]; int16 zDepth = ((int)pt0.z + pt2.z) / 2; uint insertPoint = 0; uint i = _renderShapes.size(); while (zDepth <= shapeZDepth[insertPoint] && i > 0) { insertPoint++; i--; } if (i == 0) continue; for (uint j = _renderShapes.size(); j > insertPoint; j--) { shapeZDepth[j] = shapeZDepth[j - 1]; shapeIndexes[j] = shapeIndexes[j - 1]; } shapeZDepth[insertPoint] = zDepth; shapeIndexes[insertPoint] = shpNum; } //debug("**** Begin frame ****"); for (uint shapeNum = 0; shapeNum < _renderShapes.size(); shapeNum++) { int shapeIdx = shapeIndexes[shapeNum]; _vm->_buffer2._lColor = _renderShapes[shapeIdx]._col; int numPoints = _renderShapes[shapeIdx]._pointIdxs.size(); _primX1Array.clear(); _primY1Array.clear(); _primZ1Array.clear(); _primX2Array.clear(); _primY2Array.clear(); _primZ2Array.clear(); // Link up the points in order to make a polygon. int ptIdx; for (int pointNum = 0; pointNum < numPoints - 1; pointNum++) { ptIdx = _renderShapes[shapeIdx]._pointIdxs[pointNum]; _primX1Array.push_back(_renderPoints[ptIdx].x); _primY1Array.push_back(_renderPoints[ptIdx].y); _primZ1Array.push_back(_renderPoints[ptIdx].z); ptIdx = _renderShapes[shapeIdx]._pointIdxs[pointNum + 1]; _primX2Array.push_back(_renderPoints[ptIdx].x); _primY2Array.push_back(_renderPoints[ptIdx].y); _primZ2Array.push_back(_renderPoints[ptIdx].z); } ptIdx = _renderShapes[shapeIdx]._pointIdxs[numPoints - 1]; _primX1Array.push_back(_renderPoints[ptIdx].x); _primY1Array.push_back(_renderPoints[ptIdx].y); _primZ1Array.push_back(_renderPoints[ptIdx].z); ptIdx = _renderShapes[shapeIdx]._pointIdxs[0]; _primX2Array.push_back(_renderPoints[ptIdx].x); _primY2Array.push_back(_renderPoints[ptIdx].y); _primZ2Array.push_back(_renderPoints[ptIdx].z); assert(_primX1Array.size() == (uint)numPoints); bool shouldDraw = !doPrimArrayUpdates(numPoints); if (!shouldDraw) continue; /* debug("** shape %d [%d] color %d ** ", shapeNum, shapeIdx, _vm->_buffer2._lColor); debugN("XYs: "); for (int i = 0; i < numPoints; i++) { int16 x = _primX1Array[i]; int16 y = _primY1Array[i]; debugN("(%d %d), ", x, y); } debug("."); debugN("RBs: "); for (int i = 0; i < numPoints; i++) { int16 x = _primX2Array[i]; int16 y = _primY2Array[i]; debugN("(%d %d), ", x, y); } debug("."); */ doDraw(numPoints); } //debug("**** End frame ****"); } #define POLY_DRAW_CODE 1 void MartianDuct::doDraw(int numPoints) { #if POLY_DRAW_CODE // // This code is not as efficient as the original game, but it's // easier to understand. // // We have a set of line segments in the _prim**Array arrays and want // to draw the polygons that they form, but they are not necessarily // in order or handedness. First remove null segments, then sort the // remaining ones by connecting up the start and end points. Since they // are all simple shapes this works fine. // // First remove null lines (same start and end) for (int i = 0; i < numPoints; i++) { if (_primX1Array[i] == _primX2Array[i] && _primY1Array[i] == _primY2Array[i]) { _primX1Array.remove_at(i); _primY1Array.remove_at(i); _primX2Array.remove_at(i); _primY2Array.remove_at(i); numPoints--; i--; continue; } } // Sort the line segments Common::Array x1s; Common::Array y1s; Common::Array x2s; Common::Array y2s; x1s.push_back(_primX1Array.remove_at(0)); y1s.push_back(_primY1Array.remove_at(0)); x2s.push_back(_primX2Array.remove_at(0)); y2s.push_back(_primY2Array.remove_at(0)); int pointsToAdd = numPoints - 1; while (pointsToAdd > 0) { // Find the next line segment to add, could be in either direction. for (int i = 0; i < pointsToAdd; i++) { if (_primX1Array[i] == x2s.back() && _primY1Array[i] == y2s.back()) { x1s.push_back(_primX1Array.remove_at(i)); y1s.push_back(_primY1Array.remove_at(i)); x2s.push_back(_primX2Array.remove_at(i)); y2s.push_back(_primY2Array.remove_at(i)); pointsToAdd--; break; } else if (_primX2Array[i] == x2s.back() && _primY2Array[i] == y2s.back()) { x1s.push_back(_primX2Array.remove_at(i)); y1s.push_back(_primY2Array.remove_at(i)); x2s.push_back(_primX1Array.remove_at(i)); y2s.push_back(_primY1Array.remove_at(i)); pointsToAdd--; break; } } } // Complete the shape to join up to the start again. x1s.push_back(x1s[0]); y1s.push_back(y1s[0]); _vm->_buffer2.drawPolygonScan(x1s.data(), y1s.data(), x1s.size(), Common::Rect(320, 200), _vm->_buffer2._lColor); #else int16 maxYVal = 0; int16 minYVal = 0xff; int i = 0; // Count of line segments and x1/x2 pairs for each row byte segmentCount[202]; int16 segmentCoords[200][16]; ARRAYCLEAR(segmentCount); for (int y = 0; y < 200; y++) ARRAYCLEAR(segmentCoords[y]); do { int16 primitiveX = _primXArray[i]; int16 primitiveY = _primYArray[i]; int16 primitiveB = _primY2Array[i]; int16 primitiveR = _primX2Array[i]; maxYVal = MAX(maxYVal, primitiveY); maxYVal = MAX(maxYVal, primitiveB); minYVal = MIN(minYVal, primitiveY); minYVal = MIN(minYVal, primitiveB); if (primitiveR < primitiveX) { // Flip X and Y SWAP(primitiveX, primitiveR); SWAP(primitiveY, primitiveB); } const int16 width = primitiveR - primitiveX; int16 x = primitiveX; int16 _lastPrimIdx = i; if (width == 0) { if (primitiveY != primitiveB) { if (primitiveB <= primitiveY) SWAP(primitiveY, primitiveB); int16 y = primitiveY; do { //byte bVar6 = (byte)array_a344[y * 0x10]; //*(int16 *)CONCAT11((char)((uint)(array_a344[y * 0x10] >> 8) + CARRY1(bVar6, byteData_bc44[y]), // bVar6 + byteData_bc44[y]) = primitiveX; segmentCoords[y][segmentCount[y]] = primitiveX; segmentCoords[y][segmentCount[y] + 1] = primitiveX; segmentCount[y]++; y++; } while (y != primitiveB); } } else { const int height = primitiveB - primitiveY; if (height < 0) { if (-height < width) { i = -width / 2; int16 y = primitiveY; while (true) { do { x++; i -= height; } while (i < 0); int16 nextY = y - 1; if (nextY == primitiveB) break; //byte bVar6 = (byte)array_a344[nextY * 0x10]; //*(int16 *)CONCAT11((char)((uint)(array_a344[nextY * 0x10] >> 8) + // CARRY1(bVar6,byteData_bc44[y]), // bVar6 + byteData_bc44[nextY]) = x; segmentCoords[y][segmentCount[y]] = primitiveX; segmentCoords[y][segmentCount[y] + 1] = x; segmentCount[y]++; i -= width; y = nextY; } } else { i = height / 2; int16 y = primitiveY; int16 nextY; while (nextY = y - 1, nextY != primitiveB) { //byte bVar6 = (byte)array_a344[nextY * 0x10]; //*(int16 *)CONCAT11((char)((uint)(array_a344[nextY * 0x10] >> 8) + // CARRY1(bVar6,byteData_bc44[primitiveY]), // bVar6 + byteData_bc44[primitiveY]) = x; segmentCount[-y]++; i += width; y = nextY; if (-1 < i) { x++; i += height; } } } SWAP(primitiveX, primitiveR); SWAP(primitiveY, primitiveB); //byte bVar6 = (byte)array_a344[primitiveY * 0x10]; //*(int16 *)CONCAT11((char)((uint)(array_a344[primitiveY * 0x10] >> 8) + // CARRY1(bVar6,byteData_bc44[primitiveY]), // bVar6 + byteData_bc44[primitiveY]) = primitiveX; segmentCount[primitiveY]++; } else if (height != 0 && height < width) { i = -width / 2; int16 y = primitiveY; while (true) { do { x++; i += height; } while (i < 0); //byte bVar6 = (byte)array_a344[y * 0x10]; //*(int16 *)CONCAT11((char)((uint)(array_a344[y * 0x10] >> 8) + // CARRY1(bVar6,byteData_bc44[y]), // bVar6 + byteData_bc44[y]) = x; segmentCount[y]++; y++; if (y == primitiveB) break; i -= width; } } else if (height != 0) { i = -height / 2; int16 y = primitiveY; while (true) { //byte bVar6 = (byte)array_a344[y * 0x10]; //*(int16 *)CONCAT11((char)((uint)(array_a344[y * 0x10] >> 8) + // CARRY1(bVar6,byteData_bc44[y]), // bVar6 + byteData_bc44[y]) = x; segmentCount[y]++; y++; if (y == primitiveB) break; i += width; if (-1 < i) { x++; i -= height; } } } } i = _lastPrimIdx + 1; } while (i != numPoints); segmentCount[200] = segmentCount[36]; for (int16 y = minYVal; y <= maxYVal; y++) { if (segmentCount[y] == 0) continue; int16 *lineData = segmentCoords[y]; /* CHECK ME: This probably is supposed to swap incorrect left/right vals, but seems to just check values against themselves? const int16 numSegs = segmentCount[y]; int16 right = 0; int16 left = 0; do { const int16 tmpy = lineData[right]; if (tmpy < lineData[left]) { lineData[right] = lineData[left]; lineData[left] = tmpy; } right++; } while ((right != numSegs) || (right = left + 1, left = right, right != numSegs)); */ for (int16 segNum = 0; segNum < segmentCount[y]; segNum++) { const int16 x2 = *(lineData + 1); const int16 x1 = *lineData; lineData += 2; byte *pdest = (byte *)_vm->_buffer2.getBasePtr(x1, y); for (i = 0; i < (x2 - x1) + 1; i++) { *pdest = _vm->_buffer2._lColor; pdest++; } } segmentCount[y] = 0; } #endif } void MartianDuct::doMatrixMulAndAddPoint(int16 x, int16 y, int16 z) { Point3 pt; pt.x = _matrix[0][0] * x + _matrix[1][0] * y + _matrix[2][0] * z; pt.y = _matrix[0][1] * x + _matrix[1][1] * y + _matrix[2][1] * z; pt.z = _matrix[0][2] * x + _matrix[1][2] * y + _matrix[2][2] * z; _renderPoints.push_back(pt); } bool MartianDuct::doPrimArrayUpdates(int &numPoints) { if (checkAndUpdatePrimArray1(numPoints)) return true; if (checkAndUpdatePrimArray2(numPoints)) return true; if (checkAndUpdatePrimArray3(numPoints)) return true; if (checkAndUpdatePrimArray4(numPoints)) return true; if (checkAndUpdatePrimArray5(numPoints)) return true; assert(numPoints > 0); for (int16 idx = 0; idx < numPoints; idx++) { Point3 pt1, pt2; getPointValuesFromArray(idx, pt1, pt2); // The original changes the drawing primitive coordinates here // but we only need them to store so we use a temp rect instead. const Common::Rect r = calcFinalLineSegment(pt1, pt2); _primX1Array[idx] = r.left; _primY1Array[idx] = r.top; _primX2Array[idx] = r.right; _primY2Array[idx] = r.bottom; } return false; } void MartianDuct::getPointValuesFromArray(int idx, Point3 &pt1, Point3 &pt2) const { pt1.x = _primX1Array[idx]; pt1.y = _primY1Array[idx]; pt1.z = _primZ1Array[idx]; pt2.x = _primX2Array[idx]; pt2.y = _primY2Array[idx]; pt2.z = _primZ2Array[idx]; } Common::Rect MartianDuct::calcFinalLineSegment(const Point3 &pt1, const Point3 &pt2) const { Common::Rect result; result.left = _xOffset + ((int)pt1.x * _xScale) / pt1.z; result.top = _yOffset - ((int)pt1.y * _yScale) / pt1.z; result.right = _xOffset + ((int)pt2.x * _xScale) / pt2.z; result.bottom = _yOffset - ((int)pt2.y * _yScale) / pt2.z; return result; } bool MartianDuct::checkAndUpdatePrimArrayForFlag(int &numPoints, DuctFlags flag, int divmulNum) { _primArrayIdx = 0; int tempCount = 0; for (int idx = 0; idx < numPoints; idx++) { Point3 pt1, pt2; DuctFlags xyflags; DuctFlags rbflags; getPointValuesFromArray(idx, pt1, pt2); getXYandRBFlags(xyflags, rbflags, pt1, pt2); if (xyflags == kDuctFlagNone && rbflags == kDuctFlagNone) { storeLastValsToPrimArray(pt1, pt2); // increments _primArrayIdx } else if (((xyflags | rbflags) & flag) == kDuctFlagNone) { storeLastValsToPrimArray(pt1, pt2); } else if ((xyflags & rbflags & flag) == kDuctFlagNone) { if ((rbflags & flag) == kDuctFlagNone) { // Implicitly xyflags & flag != none assert((xyflags & flag) != kDuctFlagNone); SWAP(pt1, pt2); } switch (divmulNum) { case 1: pt2 = divmul1(pt1, pt2); break; case 2: pt2 = divmul2(pt1, pt2); break; case 3: pt2 = divmul3(pt1, pt2); break; case 4: pt2 = divmul4(pt1, pt2); break; case 5: pt2 = divmul5(pt1, pt2); break; default: error("Invalid divmul num"); } _tempPoints[tempCount] = pt2; tempCount++; storeLastValsToPrimArray(pt1, pt2); } } numPoints = addPointsToMainPrimArray(tempCount); return numPoints == 0; } bool MartianDuct::checkAndUpdatePrimArray1(int &numPoints) { return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagZLessThan2, 1); } bool MartianDuct::checkAndUpdatePrimArray2(int &numPoints) { return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagXLessThanNegZ, 2); } bool MartianDuct::checkAndUpdatePrimArray3(int &numPoints) { return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagZLessThanY, 3); } bool MartianDuct::checkAndUpdatePrimArray4(int &numPoints) { return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagZLessThanX, 4); } bool MartianDuct::checkAndUpdatePrimArray5(int &numPoints) { return checkAndUpdatePrimArrayForFlag(numPoints, kDuctFlagYLessThanNegZ, 5); } // // For these functions the original uses some fixed-point stuff, but replaced // it with float to be much cleaner. // Point3 MartianDuct::divmul1(const Point3 &pt1, const Point3 &pt2) { // called for kDuctFlagXLessThanNegZ Point3 out; out.z = 2; float tmp = (2.0f - pt1.z) / (pt2.z - pt1.z); out.x = (int)round((pt2.x - pt1.x) * tmp + pt1.x); out.y = (int)round((pt2.y - pt1.y) * tmp + pt1.y); return out; } Point3 MartianDuct::divmul2(const Point3 &pt1, const Point3 &pt2) { // called for kDuctFlagXLessThanNegZ Point3 out; float tmp = (pt1.z + pt1.x * 2.0f) / ((pt1.x - pt2.x) * 2 - pt2.z + pt1.z); out.z = (int)round((pt2.z - pt1.z) * tmp + pt1.z); out.x = -(out.z / 2); out.y = (int)round((pt2.y - pt1.y) * tmp + pt1.y); return out; } Point3 MartianDuct::divmul3(const Point3 &pt1, const Point3 &pt2) { // called for kDuctFlagZLessThanY Point3 out; float tmp = (pt1.z - pt1.y * 2.0f) / ((pt2.y - pt1.y) * 2 - pt2.z + pt1.z); out.y = (int)round((pt2.z - pt1.z) * tmp + pt1.z); out.z = out.y; out.x = (int)round((pt2.x - pt1.x) * tmp + pt1.x); return out; } Point3 MartianDuct::divmul4(const Point3 &pt1, const Point3 &pt2) { // called for kDuctFlagZLessThanX Point3 out; float tmp = (pt1.z - pt1.x * 2.0f) / ((pt2.x - pt1.x) * 2 - pt2.z + pt1.z); out.z = (int)round((pt2.z - pt1.z) * tmp + pt1.z); out.x = out.z / 2; out.y = (int)round((pt2.y - pt1.y) * tmp + pt1.y); return out; } Point3 MartianDuct::divmul5(const Point3 &pt1, const Point3 &pt2) { // called for kDuctFlagYLessThanNegZ Point3 out; float tmp = (pt1.z + pt1.y * 2.0f) / ((pt1.y - pt2.y) * 2 - pt2.z + pt1.z); out.z = (int)round((pt2.z - pt1.z) * tmp + pt1.z); out.y = -(out.z / 2); out.x = (int)round((pt2.x - pt1.x) * tmp + pt1.x); return out; } void MartianDuct::getXYandRBFlags(DuctFlags &xyflags, DuctFlags &rbflags, const Point3 &pt1, const Point3 &pt2) { xyflags = getComparisonFlags(pt1.x * 2, pt1.y * 2, pt1.z); rbflags = getComparisonFlags(pt2.x * 2, pt2.y * 2, pt2.z); } int MartianDuct::addPointsToMainPrimArray(int tempCount) { int dstIdx = _primArrayIdx; // Resize arrays if we need to. if (dstIdx + tempCount > (int)_primX1Array.size()) { _primX1Array.resize(dstIdx + tempCount); _primY1Array.resize(dstIdx + tempCount); _primZ1Array.resize(dstIdx + tempCount); _primX2Array.resize(dstIdx + tempCount); _primY2Array.resize(dstIdx + tempCount); _primZ2Array.resize(dstIdx + tempCount); } for (int srcidx = 0; srcidx < tempCount; srcidx += 2) { _primX1Array[dstIdx] = _tempPoints[srcidx].x; _primY1Array[dstIdx] = _tempPoints[srcidx].y; _primZ1Array[dstIdx] = _tempPoints[srcidx].z; _primX2Array[dstIdx] = _tempPoints[srcidx + 1].x; _primY2Array[dstIdx] = _tempPoints[srcidx + 1].y; _primZ2Array[dstIdx] = _tempPoints[srcidx + 1].z; dstIdx++; } return dstIdx; } DuctFlags MartianDuct::getComparisonFlags(int16 x, int16 y, int16 z) { enum DuctFlags flags = kDuctFlagNone; if (z < 2) flags = kDuctFlagZLessThan2; if (z < y) flags = (DuctFlags)(flags | kDuctFlagZLessThanY); if (z < x) flags = (DuctFlags)(flags | kDuctFlagZLessThanX); if (x < -z) flags = (DuctFlags)(flags | kDuctFlagXLessThanNegZ); if (y < -z) flags = (DuctFlags)(flags | kDuctFlagYLessThanNegZ); return flags; } void MartianDuct::waitForMoveUpdate() { // Holding down Insert and O together skips the duct in the original. // Simplify to just accept O. if (_vm->_events->peekKeyCode() == Common::KEYCODE_o) { _vm->_room->_function = FN_CLEAR1; _vm->_flags[0x55] = 1; _vm->_flags[0x62] = 1; _stopMoveLoop = true; return; } _stopMoveLoop = false; // Wait for next frame. // Frame rate is set as 100fps, but we really want more like 30. _vm->_events->delayUntilNextFrame(); _vm->_events->delayUntilNextFrame(); _vm->_events->delayUntilNextFrame(); _vm->_events->pollEvents(); Common::CustomEventType action = _vm->_events->peekAction(); Common::Array btnCoords; for (int i = 0; Martian::DUCT_ARROW_BUTTON_RANGE[i][0] != -1; i += 2) { // DUCT_ARROW_BUTTON_RANGE is min/max X, min/max Y btnCoords.push_back(Common::Rect( Martian::DUCT_ARROW_BUTTON_RANGE[i][0], Martian::DUCT_ARROW_BUTTON_RANGE[i + 1][0], Martian::DUCT_ARROW_BUTTON_RANGE[i][1], Martian::DUCT_ARROW_BUTTON_RANGE[i + 1][1])); } // Note: buttons are 0 = up, 1 = left, 2 = down, 3 = right int hitButton = -1; if (_vm->_events->_leftButton) hitButton = _vm->_events->checkMouseBox1(btnCoords); if (action == kActionMoveUp || hitButton == 0) { _moveIntent = kMoveIntentUp; drawArrowSprites2(); if (updateMapLocation()) { checkFinished(); return; } drawArrowSprites2(); if (_moveAngle == kMoveAngleNorth) { _playerY += 7; } else if (_moveAngle == kMoveAngleSouth) { _playerY -= 7; } else if (_moveAngle == kMoveAngleEast) { _playerX += 7; } else if (_moveAngle == kMoveAngleWest) { _playerX -= 7; } else { checkFinished(); return; } _stopMoveLoop = true; _crawlFrame++; if (_crawlFrame == 7) _crawlFrame = 0; checkFinished(); return; } else if (action == kActionMoveDown || hitButton == 2) { _moveIntent = kMoveIntentDown; drawArrowSprites2(); if (updateMapLocation()) { checkFinished(); return; } drawArrowSprites2(); if (_moveAngle == kMoveAngleNorth) { _playerY -= 7; } else if (_moveAngle == kMoveAngleSouth) { _playerY += 7; } else if (_moveAngle == kMoveAngleEast) { _playerX -= 7; } else if (_moveAngle == kMoveAngleWest) { _playerX += 7; } else { checkFinished(); return; } _stopMoveLoop = true; _crawlFrame--; if (_crawlFrame < 0) _crawlFrame = 6; checkFinished(); return; } else if (action == kActionMoveLeft || hitButton == 1) { _moveIntent = kMoveIntentLeft; // Action handled, clear it _vm->_events->getAction(action); updateMapLocation(); } else if (action == kActionMoveRight || hitButton == 3) { _moveIntent = kMoveIntentRight; // Action handled, clear it _vm->_events->getAction(action); updateMapLocation(); } else { checkFinished(); return; } _moveAngle = (MoveAngle)(_moveAngle & 0xff); drawArrowSprites(); drawArrowSprites2(); _drawDistX = 200; _drawDistY = 500; if (_moveAngle != kMoveAngleNorth && _moveAngle != kMoveAngleSouth) { _drawDistX = 500; _drawDistY = 200; } _stopMoveLoop = true; } void MartianDuct::checkFinished() { // Check the player position against the exits if (abs(_playerX - 2650) < 50 && abs(_playerY - 1050) < 50) { _vm->_flags[98] = 0; _vm->_room->_function = FN_CLEAR1; } else if (abs(_playerX - 550) < 50 && abs(_playerY - 750) < 50) { // Finished! _vm->_flags[98] = 1; _vm->_flags[85] = 1; _vm->_room->_function = FN_CLEAR1; } } bool MartianDuct::updateMapLocation() { uint16 blk = _threshold1 * 2; _nextPlayerX = (_playerX / blk) * blk + _threshold1; _nextPlayerY = (_playerY / blk) * blk + _threshold1; const DuctMapPoint *mapPt = DUCT_MAP_DATA; do { const int16 pyType = mapPt->ptType; if (pyType == -1) return true; if (pyType != 0xff) { _mapLoc = pyType; int16 thresh = _threshold1; if (pyType == 6 || pyType == 10) thresh = -_threshold1; if (abs((mapPt->x + thresh) - _nextPlayerX) <= _threshold1) { thresh = _threshold1; if (pyType == 8) thresh = -_threshold1; if (abs((mapPt->y + thresh) - _nextPlayerY) <= _threshold1) { switch (pyType) { case 0: return checkMove0(); case 1: return checkMove1(); case 2: return checkMove2(); case 3: return checkMove3(); case 4: return checkMove4(); case 5: return checkMove5(); case 6: return checkMove6(); case 7: return checkMove7(); case 8: return checkMove8(); case 9: return checkMove9(); case 10: return checkMove10(); case 11: return checkMove11(); case 12: return checkMove12(); case 13: return checkMove13_14(); case 14: return checkMove13_14(); default: error("Unexpected point type in duct map data %d", pyType); return false; } } } } mapPt++; } while( true ); } void MartianDuct::storeLastValsToPrimArray(const Point3 &pt1, const Point3 &pt2) { _primX1Array[_primArrayIdx] = pt1.x; _primY1Array[_primArrayIdx] = pt1.y; _primZ1Array[_primArrayIdx] = pt1.z; _primX2Array[_primArrayIdx] = pt2.x; _primY2Array[_primArrayIdx] = pt2.y; _primZ2Array[_primArrayIdx] = pt2.z; _primArrayIdx++; } bool MartianDuct::checkMove0() { if (_moveIntent == kMoveIntentRight || _moveIntent == kMoveIntentLeft) { _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth); return false; } if (_moveIntent == kMoveIntentUp) { if (_moveAngle == kMoveAngleWest) return true; } else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleEast) { return true; } return false; } bool MartianDuct::checkMove1() { if ((_moveIntent == kMoveIntentRight) || (_moveIntent == kMoveIntentLeft)) { _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth); return false; } if (_moveIntent == kMoveIntentUp) { if (_moveAngle == kMoveAngleEast) return true; } else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleWest) { return true; } return false; } bool MartianDuct::checkMove2() { if (_moveIntent == kMoveIntentRight || _moveIntent == kMoveIntentLeft) { _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth); return false; } if (_moveIntent == kMoveIntentUp) { if (_moveAngle == kMoveAngleSouth) return true; } else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleNorth) { return true; } return false; } bool MartianDuct::checkMove3() { if (_moveIntent == kMoveIntentRight || _moveIntent == kMoveIntentLeft) { _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth); return false; } if (_moveIntent == kMoveIntentUp) { if (_moveAngle == kMoveAngleNorth) return true; } else if (_moveIntent == kMoveIntentDown && _moveAngle == kMoveAngleSouth) { return true; } return false; } bool MartianDuct::checkMove4() { if (_moveIntent == kMoveIntentRight) { _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) return false; _moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove5() { if (_moveIntent == kMoveIntentRight) { if (_moveAngle == kMoveAngleSouth) return true; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleWest; if (_moveIntent != kMoveIntentDown) return true; if (_moveAngle != kMoveAngleEast) return false; return true; } if (_moveAngle == kMoveAngleNorth) return true; _moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove6() { if (_moveIntent == kMoveIntentRight) { if (_moveAngle == kMoveAngleNorth) return true; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleEast; if (_moveIntent != kMoveIntentDown) return true; if (_moveAngle != kMoveAngleWest) return false; return true; } if (_moveAngle == kMoveAngleSouth) return true; _moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove7() { if (_moveIntent == kMoveIntentRight) { if (_moveAngle == kMoveAngleEast) return true; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleSouth; if (_moveIntent != kMoveIntentDown) return true; if (_moveAngle != kMoveAngleNorth) return false; return true; } if (_moveAngle == kMoveAngleWest) return true; _moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove8() { if (_moveIntent == kMoveIntentRight) { if (_moveAngle == kMoveAngleWest) return true; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleNorth; if (_moveIntent != kMoveIntentDown) return true; if (_moveAngle != kMoveAngleSouth) return false; return true; } if (_moveAngle == kMoveAngleEast) return true; _moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove9() { if (_moveIntent == kMoveIntentRight) { if ((_moveAngle != kMoveAngleNorth) && (_moveAngle != kMoveAngleEast)) { return true; } _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleWest || _moveAngle == kMoveAngleNorth; if (_moveIntent != kMoveIntentDown) return true; if (_moveAngle != kMoveAngleEast) return _moveAngle == kMoveAngleSouth; return true; } if (_moveAngle != kMoveAngleWest && _moveAngle != kMoveAngleSouth) return true; _moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove10() { if (_moveIntent == kMoveIntentRight) { if (_moveAngle != kMoveAngleEast && _moveAngle != kMoveAngleSouth) return true; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleEast || _moveAngle == kMoveAngleNorth; if (_moveIntent != kMoveIntentDown) return true; if (_moveAngle != kMoveAngleWest) return _moveAngle == kMoveAngleSouth; return true; } if (_moveAngle != kMoveAngleNorth && _moveAngle != kMoveAngleWest) return true; _moveAngle = (MoveAngle)((_moveAngle - kMoveAngleEast) & 0xff); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove11() { if (_moveIntent == kMoveIntentRight) { if (_moveAngle != kMoveAngleWest && _moveAngle != kMoveAngleNorth) return true; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleWest || _moveAngle == kMoveAngleSouth; if (_moveIntent != kMoveIntentDown) { return true; } if (_moveAngle != kMoveAngleNorth) { return _moveAngle == kMoveAngleEast; } return true; } if (_moveAngle != kMoveAngleSouth && _moveAngle != kMoveAngleEast) return true; _moveAngle = (MoveAngle)(_moveAngle - kMoveAngleEast); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove12() { if (_moveIntent == kMoveIntentRight) { if (_moveAngle != kMoveAngleSouth && _moveAngle != kMoveAngleWest) return true; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleEast); } else { if (_moveIntent != kMoveIntentLeft) { if (_moveIntent == kMoveIntentUp) return _moveAngle == kMoveAngleEast || _moveAngle == kMoveAngleSouth; if (_moveIntent != kMoveIntentDown) return true; if (_moveAngle != kMoveAngleNorth) return _moveAngle == kMoveAngleWest; return true; } if (_moveAngle != kMoveAngleEast && _moveAngle != kMoveAngleNorth) return true; _moveAngle = (MoveAngle)((_moveAngle - kMoveAngleEast) & 0xff); } _playerY = _nextPlayerY; _playerX = _nextPlayerX; return false; } bool MartianDuct::checkMove13_14() { if (_moveIntent != kMoveIntentRight && _moveIntent != kMoveIntentLeft) return false; _moveAngle = (MoveAngle)(_moveAngle + kMoveAngleSouth); return false; } } } // end namespace Access