/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/config-manager.h" #include "xeen/scripts.h" #include "xeen/dialogs/dialogs_copy_protection.h" #include "xeen/dialogs/dialogs_input.h" #include "xeen/dialogs/dialogs_whowill.h" #include "xeen/dialogs/dialogs_query.h" #include "xeen/party.h" #include "xeen/resources.h" #include "xeen/xeen.h" namespace Xeen { byte EventParameters::Iterator::readByte() { byte result = (_index >= _data.size()) ? 0 : _data[_index]; ++_index; return result; } uint16 EventParameters::Iterator::readUint16LE() { uint16 result = ((_index + 1) >= _data.size()) ? 0 : READ_LE_UINT16(&_data[_index]); _index += 2; return result; } uint32 EventParameters::Iterator::readUint32LE() { uint16 result = ((_index + 3) >= _data.size()) ? 0 : READ_LE_UINT32(&_data[_index]); _index += 4; return result; } /*------------------------------------------------------------------------*/ MazeEvent::MazeEvent() : _direction(DIR_ALL), _line(-1), _opcode(OP_None) { } void MazeEvent::synchronize(Common::Serializer &s) { int len = 5 + _parameters.size(); s.syncAsByte(len); s.syncAsByte(_position.x); s.syncAsByte(_position.y); s.syncAsByte(_direction); s.syncAsByte(_line); s.syncAsByte(_opcode); len -= 5; if (s.isLoading()) _parameters.resize(len); for (int i = 0; i < len; ++i) s.syncAsByte(_parameters[i]); } /*------------------------------------------------------------------------*/ void MazeEvents::synchronize(XeenSerializer &s) { MazeEvent e; if (s.isLoading()) { clear(); while (!s.finished()) { e.synchronize(s); push_back(e); } } else { for (uint i = 0; i < size(); ++i) (*this).operator[](i).synchronize(s); } } /*------------------------------------------------------------------------*/ bool MirrorEntry::synchronize(Common::SeekableReadStream &s) { if (s.pos() >= s.size()) return false; char buffer[28]; s.read(buffer, 28); buffer[27] = '\0'; _name = Common::String(buffer); _mapId = s.readByte(); _position.x = s.readSByte(); _position.y = s.readSByte(); _direction = s.readSByte(); return true; } /*------------------------------------------------------------------------*/ Scripts::Scripts(XeenEngine *vm) : _vm(vm) { _whoWill = 0; _itemType = 0; _treasureItems = 0; _lineNum = 0; _charIndex = 0; _nEdamageType = DT_PHYSICAL; _animCounter = 0; _eventSkipped = false; _mirrorId = -1; _refreshIcons = false; _scriptResult = false; _scriptExecuted = false; _dirFlag = false; _redrawDone = false; _windowIndex = -1; _event = nullptr; } int Scripts::checkEvents() { Combat &combat = *_vm->_combat; EventsManager &events = *_vm->_events; FileManager &files = *_vm->_files; Interface &intf = *_vm->_interface; Map &map = *_vm->_map; Party &party = *_vm->_party; Sound &sound = *_vm->_sound; Windows &windows = *_vm->_windows; int ccNum = files._ccNum; _refreshIcons = false; _itemType = 0; _scriptExecuted = false; _dirFlag = false; _whoWill = 0; Mode oldMode = _vm->_mode; Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0); if (party._treasure._gold & party._treasure._gems) { // Backup any current treasure data party._savedTreasure = party._treasure; party._treasure._hasItems = false; party._treasure._gold = 0; party._treasure._gems = 0; } else { party._savedTreasure._hasItems = false; party._savedTreasure._gold = 0; party._savedTreasure._gems = 0; } do { _lineNum = 0; _scriptResult = false; _animCounter = 0; _redrawDone = false; _currentPos = party._mazePosition; _charIndex = 1; combat._combatTarget = 1; _nEdamageType = DT_PHYSICAL; while (!_vm->shouldExit() && _lineNum >= 0) { // Stop processing events if there's an attacking monster if (combat._attackMonsters[0] != -1) { _eventSkipped = true; _lineNum = SCRIPT_ABORT; break; } uint eventIndex; for (eventIndex = 0; eventIndex < map._events.size() && !_vm->shouldExit(); ++eventIndex) { MazeEvent &event = map._events[eventIndex]; if (event._position == _currentPos && event._line == _lineNum && (party._mazeDirection | _currentPos.x | _currentPos.y)) { if (event._direction == party._mazeDirection || event._direction == DIR_ALL) { _vm->_mode = MODE_SCRIPT_IN_PROGRESS; _scriptExecuted = true; doOpcode(event); break; } else { _dirFlag = true; } } } if (eventIndex == map._events.size()) _lineNum = SCRIPT_ABORT; } } while (!_vm->shouldExit() && _lineNum != SCRIPT_ABORT); intf._face1State = intf._face2State = 2; if (_refreshIcons) { windows.closeAll(); intf.drawParty(true); } party.checkPartyDead(); if (party._treasure._hasItems || party._treasure._gold || party._treasure._gems) party.giveTreasure(); if (_animCounter > 0 && intf._objNumber != -1) { MazeObject &selectedObj = map._mobData._objects[intf._objNumber]; if (selectedObj._spriteId == (ccNum ? 15 : 16)) { for (int idx = 0; idx < MIN((int)map._mobData._objects.size(), 16); ++idx) { MazeObject &obj = map._mobData._objects[idx]; if (obj._spriteId == (ccNum ? 62 : 57)) { selectedObj._id = idx; selectedObj._spriteId = ccNum ? 62 : 57; break; } } } else if (selectedObj._spriteId == 73) { for (int idx = 0; idx < MIN((int)map._mobData._objects.size(), 16); ++idx) { MazeObject &obj = map._mobData._objects[idx]; if (obj._spriteId == 119) { selectedObj._id = idx; selectedObj._spriteId = 119; break; } } } } _animCounter = 0; _vm->_mode = oldMode; windows.closeAll(); if (_scriptExecuted) intf.clearEvents(); if (_scriptExecuted || intf._objNumber == -1 || _dirFlag) { if (_dirFlag && !_scriptExecuted && intf._objNumber != -1 && !map._currentIsEvent) { sound.playFX(21); } } else { Window &w = windows[38]; w.open(); w.writeString(Res.NOTHING_HERE); w.update(); do { intf.draw3d(true); events.updateGameCounter(); events.wait(1); } while (!events.isKeyMousePressed() && !_vm->shouldExit()); events.clearEvents(); w.close(); } // Restore saved treasure if (party._savedTreasure._hasItems || party._savedTreasure._gold || party._savedTreasure._gems) { party._treasure = party._savedTreasure; } combat._combatTarget = 1; Common::fill(&intf._charFX[0], &intf._charFX[6], 0); return _scriptResult; } bool Scripts::openGrate(int wallVal, int action) { Combat &combat = *_vm->_combat; FileManager &files = *_vm->_files; Interface &intf = *_vm->_interface; Map &map = *_vm->_map; Party &party = *_vm->_party; Sound &sound = *_vm->_sound; int ccNum = files._ccNum; if (!((wallVal != 13 || map._currentGrateUnlocked) && (!ccNum || wallVal != 9 || map.mazeData()._wallKind != 2))) return false; if (wallVal != 9 && !map._currentGrateUnlocked) { int charIndex = WhoWill::show(_vm, 13, action, false) - 1; if (charIndex < 0) { intf.draw3d(true); return true; } // There is a 1 in 4 chance the character will receive damage if (_vm->getRandomNumber(1, 4) == 1) { combat.giveCharDamage(map.mazeData()._trapDamage, (DamageType)_vm->getRandomNumber(0, 6), charIndex); } // Check whether character can unlock the door Character &c = party._activeParty[charIndex]; if ((c.getThievery() + _vm->getRandomNumber(1, 20)) < map.mazeData()._difficulties._unlockDoor) return true; c._experience += map.mazeData()._difficulties._unlockDoor * c.getCurrentLevel(); } // Flag the grate as unlocked, and the wall the grate is on map.setCellSurfaceFlags(party._mazePosition, 0x80); map.setWall(party._mazePosition, party._mazeDirection, wallVal); // Set the grate as opened and the wall on the other side of the grate Common::Point pt = party._mazePosition; Direction dir = (Direction)((int)party._mazeDirection ^ 2); switch (party._mazeDirection) { case DIR_NORTH: pt.y++; break; case DIR_EAST: pt.x++; break; case DIR_SOUTH: pt.y--; break; case DIR_WEST: pt.x--; break; default: break; } map.setCellSurfaceFlags(pt, 0x80); map.setWall(pt, dir, wallVal); sound.playFX(10); intf.draw3d(true); return true; } bool Scripts::doOpcode(MazeEvent &event) { Map &map = *_vm->_map; typedef bool(Scripts::*ScriptMethodPtr)(ParamsIterator &); static const ScriptMethodPtr COMMAND_LIST[] = { &Scripts::cmdDoNothing, &Scripts::cmdDisplay1, &Scripts::cmdDoorTextSml, &Scripts::cmdDoorTextLrg, &Scripts::cmdSignText, &Scripts::cmdNPC, &Scripts::cmdPlayFX, &Scripts::cmdTeleport, &Scripts::cmdIf, &Scripts::cmdIf, &Scripts::cmdIf, &Scripts::cmdMoveObj, &Scripts::cmdTakeOrGive, &Scripts::cmdDoNothing, &Scripts::cmdRemove, &Scripts::cmdSetChar, &Scripts::cmdSpawn, &Scripts::cmdDoTownEvent, &Scripts::cmdExit, &Scripts::cmdAlterMap, &Scripts::cmdGiveMulti, &Scripts::cmdConfirmWord, &Scripts::cmdDamage, &Scripts::cmdJumpRnd, &Scripts::cmdAlterEvent, &Scripts::cmdCallEvent, &Scripts::cmdReturn, &Scripts::cmdSetVar, &Scripts::cmdTakeOrGive, &Scripts::cmdTakeOrGive, &Scripts::cmdCutsceneEndClouds, &Scripts::cmdTeleport, &Scripts::cmdWhoWill, &Scripts::cmdRndDamage, &Scripts::cmdMoveWallObj, &Scripts::cmdAlterCellFlag, &Scripts::cmdAlterHed, &Scripts::cmdDisplayStat, &Scripts::cmdTakeOrGive, &Scripts::cmdSignTextSml, &Scripts::cmdPlayEventVoc, &Scripts::cmdDisplayBottom, &Scripts::cmdIfMapFlag, &Scripts::cmdSelectRandomChar, &Scripts::cmdGiveEnchanted, &Scripts::cmdItemType, &Scripts::cmdMakeNothingHere, &Scripts::cmdCheckProtection, &Scripts::cmdChooseNumeric, &Scripts::cmdDisplayBottomTwoLines, &Scripts::cmdDisplayLarge, &Scripts::cmdExchObj, &Scripts::cmdFallToMap, &Scripts::cmdDisplayMain, &Scripts::cmdGoto, &Scripts::cmdConfirmWord, &Scripts::cmdGotoRandom, &Scripts::cmdCutsceneEndDarkside, &Scripts::cmdCutsceneEndWorld, &Scripts::cmdFlipWorld, &Scripts::cmdPlayCD }; _event = &event; // Some opcodes use the first parameter as a message uint msgId = event._parameters.empty() ? 0 : event._parameters[0]; _message = msgId >= map._events._text.size() ? "" : map._events._text[msgId]; // Execute the opcode ParamsIterator params = event._parameters.getIterator(); bool result = (this->*COMMAND_LIST[event._opcode])(params); if (result) // Move to next line _lineNum = _vm->_party->_dead ? SCRIPT_ABORT : _lineNum + 1; return result; } bool Scripts::cmdDoNothing(ParamsIterator ¶ms) { return true; } bool Scripts::cmdDisplay1(ParamsIterator ¶ms) { Windows &windows = *_vm->_windows; Common::String paramText = _vm->_map->_events._text[params.readByte()]; Common::String msg = Common::String::format("\r\x03""c%s", paramText.c_str()); windows[12].close(); if (!windows[38]._enabled) windows[38].open(); windows[38].writeString(msg); windows[38].update(); return true; } bool Scripts::cmdDoorTextSml(ParamsIterator ¶ms) { Interface &intf = *_vm->_interface; Common::String paramText = _vm->_map->_events._text[params.readByte()]; intf._screenText = Common::String::format("\x02\f""08\x03""c\t116\v025%s\x03""l\fd""\x01", paramText.c_str()); intf._upDoorText = true; intf.draw3d(true); return true; } bool Scripts::cmdDoorTextLrg(ParamsIterator ¶ms) { Interface &intf = *_vm->_interface; Common::String paramText = _vm->_map->_events._text[params.readByte()]; intf._screenText = Common::String::format("\f04\x03""c\t116\v030%s\x03""l\fd", paramText.c_str()); intf._upDoorText = true; intf.draw3d(true); return true; } bool Scripts::cmdSignText(ParamsIterator ¶ms) { Interface &intf = *_vm->_interface; Common::String paramText = _vm->_map->_events._text[params.readByte()]; intf._screenText = Common::String::format("\f08\x03""c\t120\v088%s\x03""l\fd", paramText.c_str()); intf._upDoorText = true; intf.draw3d(true); return true; } bool Scripts::cmdNPC(ParamsIterator ¶ms) { Map &map = *_vm->_map; params.readByte(); // _message already holds title int textNum = params.readByte(); int portrait = params.readByte(); int confirm = params.readByte(); int lineNum = params.readByte(); if (LocationMessage::show(portrait, _message, map._events._text[textNum], confirm)) { _lineNum = lineNum; return false; } return true; } bool Scripts::cmdPlayFX(ParamsIterator ¶ms) { _vm->_sound->playFX(params.readByte()); return true; } bool Scripts::cmdTeleport(ParamsIterator ¶ms) { EventsManager &events = *_vm->_events; Interface &intf = *_vm->_interface; Map &map = *_vm->_map; Party &party = *_vm->_party; Windows &windows = *_vm->_windows; Sound &sound = *_vm->_sound; windows.closeAll(); bool restartFlag = _event->_opcode == OP_TeleportAndContinue; int mapId = params.readByte(); Common::Point pt; if (mapId) { // Specific map, x & y specified pt.x = params.readShort(); pt.y = params.readShort(); } else { // Mirror teleportation assert(_mirrorId > 0); MirrorEntry &me = _mirror[_mirrorId - 1]; mapId = me._mapId; pt = me._position; if (me._direction != -1) party._mazeDirection = (Direction)me._direction; if (pt.x == 0 && pt.y == 0) pt.x = 999; sound.playFX(51); } party._stepped = true; if (mapId != party._mazeId) { int spriteId = (intf._objNumber == -1) ? -1 : map._mobData._objects[intf._objNumber]._spriteId; switch (spriteId) { case 47: sound.playFX(45); break; case 48: sound.playFX(44); break; default: break; } // Load the new map map.load(mapId); } if (pt.x == 999) { party.moveToRunLocation(); } else { party._mazePosition = pt; } events.clearEvents(); if (restartFlag) { // Draw the new location and start any script at that location events.ipause(2); _lineNum = SCRIPT_RESET; return false; } else { // Stop executing the script return cmdExit(params); } } bool Scripts::cmdIf(ParamsIterator ¶ms) { Combat &combat = *_vm->_combat; Party &party = *_vm->_party; uint32 val; int newLineNum; int mode = params.readByte(); switch (mode) { case 16: case 34: case 100: val = params.readUint32LE(); break; case 25: case 35: case 101: case 106: val = params.readUint16LE(); break; default: val = params.readByte(); break; } newLineNum = params.readByte(); bool result; if ((_charIndex != 0 && _charIndex != 8) || mode == 44) { result = ifProc(mode, val, _event->_opcode - 8, _charIndex - 1); } else { result = false; for (int idx = 0; idx < (int)party._activeParty.size() && !result; ++idx) { if (_charIndex == 0 || (_charIndex == 8 && (int)idx != combat._combatTarget)) { result = ifProc(mode, val, _event->_opcode - 8, idx); } } } if (result) { _lineNum = newLineNum; return false; } return true; } bool Scripts::cmdMoveObj(ParamsIterator ¶ms) { MazeObject &mazeObj = _vm->_map->_mobData._objects[params.readByte()]; int x = params.readShort(), y = params.readShort(); if (mazeObj._position.x == x && mazeObj._position.y == y) { // Already in position, so simply flip it mazeObj._flipped = !mazeObj._flipped; } else { mazeObj._position.x = x; mazeObj._position.y = y; } return true; } bool Scripts::cmdTakeOrGive(ParamsIterator ¶ms) { Combat &combat = *_vm->_combat; Party &party = *_vm->_party; Windows &windows = *_vm->_windows; int mode1, mode2, mode3; uint32 val1, val2, val3; _refreshIcons = true; mode1 = params.readByte(); switch (mode1) { case 16: case 34: case 100: val1 = params.readUint32LE(); break; case 25: case 35: case 101: case 106: val1 = params.readUint16LE(); break; default: val1 = params.readByte(); break; } mode2 = params.readByte(); switch (mode2) { case 16: case 34: case 100: val2 = params.readUint32LE(); break; case 25: case 35: case 101: case 106: val2 = params.readUint16LE(); break; default: val2 = params.readByte(); break; } mode3 = params.readByte(); switch (mode3) { case 16: case 34: case 100: val3 = params.readUint32LE(); break; case 25: case 35: case 101: case 106: val3 = params.readUint16LE(); break; default: val3 = params.readByte(); break; } if (mode2 == 67) windows.closeAll(); switch (_event->_opcode) { case OP_TakeOrGive_2: if (_charIndex == 0 || _charIndex == 8) { for (uint idx = 0; idx < party._activeParty.size(); ++idx) { if (_charIndex == 0 || (_charIndex == 8 && (int)idx != combat._combatTarget)) { if (ifProc(mode1, val1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, idx)) { party.giveTake(0, 0, mode2, val2, idx); if (mode2 == 82) break; } } } } else if (ifProc(mode1, val1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, _charIndex - 1)) { party.giveTake(0, 0, mode2, val2, _charIndex - 1); } break; case OP_TakeOrGive_3: if (_charIndex == 0 || _charIndex == 8) { for (uint idx = 0; idx < party._activeParty.size(); ++idx) { if (_charIndex == 0 || (_charIndex == 8 && (int)idx != combat._combatTarget)) { if (ifProc(mode1, val1, 1, idx) && ifProc(mode2, val2, 1, idx)) { party.giveTake(0, 0, mode2, val3, idx); if (mode2 == 82) break; } } } } else if (ifProc(mode1, val1, 1, _charIndex - 1) && ifProc(mode2, val2, 1, _charIndex - 1)) { party.giveTake(0, 0, mode2, val3, _charIndex - 1); } break; case OP_TakeOrGive_4: if (_charIndex == 0 || _charIndex == 8) { for (uint idx = 0; idx < party._activeParty.size(); ++idx) { if (_charIndex == 0 || (_charIndex == 8 && (int)idx != combat._combatTarget)) { if (ifProc(mode1, val1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, idx)) { party.giveTake(0, 0, mode2, val2, idx); if (mode2 == 82) break; } } } } else if (ifProc(mode1, val1, 1, _charIndex - 1)) { party.giveTake(0, 0, mode2, val2, _charIndex - 1); } break; default: if (_charIndex == 0 || _charIndex == 8) { for (uint idx = 0; idx < party._activeParty.size(); ++idx) { if (_charIndex == 0 || (_charIndex == 8 && (int)idx != combat._combatTarget)) { bool flag = party.giveTake(mode1, val1, mode2, val2, idx); switch (mode1) { case 8: mode1 = 0; // fall through case 21: case 66: if (flag) { switch (mode2) { case 82: mode1 = 0; // fall through case 21: case 34: case 35: case 65: case 66: case 100: case 101: case 106: if (flag) continue; // Break out of character loop idx = party._activeParty.size(); break; default: break; } } else { // Break out of character loop idx = party._activeParty.size(); } break; case 34: case 35: case 65: case 100: case 101: case 106: if (flag) { _lineNum = -1; return false; } // Break out of character loop idx = party._activeParty.size(); break; default: switch (mode2) { case 82: mode1 = 0; // fall through case 21: case 34: case 35: case 65: case 66: case 100: case 101: case 106: if (flag) continue; // Break out of character loop idx = party._activeParty.size(); break; } break; } } } } else { if (!party.giveTake(mode1, val1, mode2, val2, _charIndex - 1)) { if (mode2 == 79) windows.closeAll(); } } break; } return true; } bool Scripts::cmdRemove(ParamsIterator ¶ms) { Interface &intf = *_vm->_interface; Map &map = *_vm->_map; if (intf._objNumber != -1) { // Give the active object a completely way out of bounds position MazeObject &obj = map._mobData._objects[intf._objNumber]; obj._position = Common::Point(128, 128); } cmdMakeNothingHere(params); return true; } bool Scripts::cmdSetChar(ParamsIterator ¶ms) { Combat &combat = *_vm->_combat; int charId = params.readByte(); if (charId == 0) { _charIndex = 0; combat._combatTarget = 0; } else if (charId < 7) { combat._combatTarget = charId; } else if (charId == 7) { _charIndex = _vm->getRandomNumber(1, _vm->_party->_activeParty.size()); combat._combatTarget = 1; } else { _charIndex = WhoWill::show(_vm, 22, 3, false); if (_charIndex == 0) return cmdExit(params); } return true; } bool Scripts::cmdSpawn(ParamsIterator ¶ms) { Map &map = *_vm->_map; uint index = params.readByte(); if (index >= map._mobData._monsters.size()) map._mobData._monsters.resize(index + 1); MazeMonster &monster = _vm->_map->_mobData._monsters[index]; MonsterStruct &monsterData = _vm->_map->_monsterData[monster._spriteId]; monster._monsterData = &monsterData; monster._position.x = params.readShort(); monster._position.y = params.readShort(); monster._frame = _vm->getRandomNumber(7); monster._damageType = DT_PHYSICAL; monster._isAttacking = false; monster._hp = monsterData._hp; return true; } bool Scripts::cmdDoTownEvent(ParamsIterator ¶ms) { _scriptResult = _vm->_locations->doAction(params.readByte()); _vm->_party->_stepped = true; _refreshIcons = true; return cmdExit(params); } bool Scripts::cmdExit(ParamsIterator ¶ms) { _lineNum = SCRIPT_ABORT; return false; } bool Scripts::cmdAlterMap(ParamsIterator ¶ms) { Map &map = *_vm->_map; int x = params.readShort(); int y = params.readShort(); Direction dir = (Direction)params.readByte(); int val = params.readByte(); if (dir == DIR_ALL) { for (dir = DIR_NORTH; dir <= DIR_WEST; dir = (Direction)((int)dir + 1)) map.setWall(Common::Point(x, y), dir, val); } else { map.setWall(Common::Point(x, y), dir, val); } return true; } bool Scripts::cmdGiveMulti(ParamsIterator ¶ms) { Party &party = *_vm->_party; int modes[3]; uint32 vals[3]; _refreshIcons = true; for (int idx = 0; idx < 3; ++idx) { modes[idx] = params.readByte(); switch (modes[idx]) { case 16: case 34: case 100: vals[idx] = params.readUint32LE(); break; case 25: case 35: case 101: case 106: vals[idx] = params.readUint16LE(); break; default: vals[idx] = params.readByte(); break; } } _scriptExecuted = true; bool result = party.giveExt(modes[0], vals[0], modes[1], vals[1], modes[2], vals[2], (_charIndex > 0) ? _charIndex - 1 : 0); if (result) { if (_animCounter == 255) { _animCounter = 0; return cmdExit(params); } else if (modes[0] == 67 || modes[1] == 67 || modes[2] == 67) { _animCounter = 1; } else { return cmdExit(params); } } else { if (modes[0] == 67 || modes[1] == 67 || modes[2] == 67) return cmdExit(params); } return true; } bool Scripts::cmdConfirmWord(ParamsIterator ¶ms) { FileManager &files = *_vm->_files; Map &map = *_vm->_map; Party &party = *_vm->_party; int inputType = params.readByte(); int lineNum = params.readByte(); int param2 = params.readByte(); int param3 = params.readByte(); Common::String expected2; Common::String title; if (_event->_opcode == OP_ConfirmWord_2) { title = ""; } else if (param3) { title = map._events._text[param3]; } else { title = Res.WHATS_THE_PASSWORD; } if (!param2) { expected2 = _message; } else if (param2 < (int)map._events._text.size()) { expected2 = map._events._text[param2]; } _mirrorId = StringInput::show(_vm, inputType, expected2, title, _event->_opcode); if (_mirrorId) { if (_mirrorId == 33 && files._ccNum) { doDarkSideEnding(); } else if (_mirrorId == 34 && files._ccNum) { doWorldEnding(); } else if (_mirrorId == 35 && files._ccNum && _vm->getGameID() == GType_WorldOfXeen) { doCloudsEnding(); } else if (_mirrorId == 40 && !files._ccNum) { doCloudsEnding(); } else if (_mirrorId == 60 && !files._ccNum) { doDarkSideEnding(); } else if (_mirrorId == 61 && !files._ccNum) { doWorldEnding(); } else { if (_mirrorId == 59 && !files._ccNum) { for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) { XeenItem &item = party._treasure._weapons[idx]; if (!item._id) { item._id = XEEN_SLAYER_SWORD; item._material = 0; item._state.clear(); party._treasure._hasItems = true; return cmdExit(params); } } } _lineNum = _mirrorId == -1 ? param3 : lineNum; return false; } } return true; } bool Scripts::cmdDamage(ParamsIterator ¶ms) { Combat &combat = *_vm->_combat; Interface &intf = *_vm->_interface; if (!_redrawDone) { intf.draw3d(true); _redrawDone = true; } int damage = params.readUint16LE(); DamageType damageType = (DamageType)params.readByte(); combat.giveCharDamage(damage, damageType, _charIndex - 1); return true; } bool Scripts::cmdJumpRnd(ParamsIterator ¶ms) { int v = _vm->getRandomNumber(1, params.readByte()); if (v == params.readByte()) { _lineNum = params.readByte(); return false; } return true; } bool Scripts::cmdAlterEvent(ParamsIterator ¶ms) { Map &map = *_vm->_map; Party &party = *_vm->_party; int lineNum = params.readByte(); Opcode opcode = (Opcode)params.readByte(); for (uint idx = 0; idx < map._events.size(); ++idx) { MazeEvent &evt = map._events[idx]; if (evt._position == party._mazePosition && (evt._direction == DIR_ALL || evt._direction == party._mazeDirection) && evt._line == lineNum) { evt._opcode = opcode; } } return true; } bool Scripts::cmdCallEvent(ParamsIterator ¶ms) { _stack.push(StackEntry(_currentPos, _lineNum)); _currentPos.x = params.readShort(); _currentPos.y = params.readShort(); _lineNum = params.readByte(); return false; } bool Scripts::cmdReturn(ParamsIterator ¶ms) { if (_stack.empty()) { // WORKAROUND: Some scripts in Swords of Xeen use cmdReturn as a substitute for cmdExit return cmdExit(params); } else { StackEntry se = _stack.pop(); _currentPos = se; _lineNum = se.line; return true; } } bool Scripts::cmdSetVar(ParamsIterator ¶ms) { Combat &combat = *_vm->_combat; Party &party = *_vm->_party; uint val; _refreshIcons = true; int mode = params.readByte(); switch (mode) { case 25: case 35: case 101: case 106: val = params.readUint16LE(); break; case 16: case 34: case 100: val = params.readUint32LE(); break; default: val = params.readByte(); break; } if (_charIndex != 0 && _charIndex != 8) { party._activeParty[_charIndex - 1].setValue(mode, val); } else { // Set value for entire party for (int idx = 0; idx < (int)party._activeParty.size(); ++idx) { if (_charIndex == 0 || (_charIndex == 8 && combat._combatTarget != idx)) { party._activeParty[idx].setValue(mode, val); } } } return true; } bool Scripts::cmdCutsceneEndClouds(ParamsIterator ¶ms) { Party &party = *_vm->_party; party._gameFlags[0][75] = true; party._mazeId = 28; party._mazePosition = Common::Point(18, 4); g_vm->_gameWon[0] = true; g_vm->_finalScore = party.getScore(); g_vm->saveSettings(); doCloudsEnding(); return false; } bool Scripts::cmdWhoWill(ParamsIterator ¶ms) { int msg = params.readByte(); int action = params.readByte(); _charIndex = WhoWill::show(_vm, msg, action, true); if (_charIndex == 0) return cmdExit(params); else return true; } bool Scripts::cmdRndDamage(ParamsIterator ¶ms) { Combat &combat = *_vm->_combat; Interface &intf = *_vm->_interface; if (!_redrawDone) { intf.draw3d(true); _redrawDone = true; } DamageType dmgType = (DamageType)params.readByte(); int max = params.readByte(); combat.giveCharDamage(_vm->getRandomNumber(1, max), dmgType, _charIndex - 1); return true; } bool Scripts::cmdMoveWallObj(ParamsIterator ¶ms) { Map &map = *_vm->_map; int index = params.readByte(); int x = params.readShort(); int y = params.readShort(); map._mobData._wallItems[index]._position = Common::Point(x, y); return true; } bool Scripts::cmdAlterCellFlag(ParamsIterator ¶ms) { Map &map = *_vm->_map; Common::Point pt; pt.x = params.readShort(); pt.y = params.readShort(); int surfaceId = params.readByte(); map.cellFlagLookup(pt); if (map._isOutdoors) { MazeWallLayers &wallData = map.mazeDataCurrent()._wallData[pt.y][pt.x]; wallData._data = (wallData._data & 0xFFF0) | surfaceId; } else { pt.x &= 0xF; pt.y &= 0xF; MazeCell &cell = map.mazeDataCurrent()._cells[pt.y][pt.x]; cell._surfaceId = surfaceId; } return true; } bool Scripts::cmdAlterHed(ParamsIterator ¶ms) { Map &map = *_vm->_map; Party &party = *_vm->_party; HeadData::HeadEntry &he = map._headData[party._mazePosition.y][party._mazePosition.x]; he._left = params.readByte(); he._right = params.readByte(); return true; } bool Scripts::cmdDisplayStat(ParamsIterator ¶ms) { Party &party = *_vm->_party; Windows &windows = *_vm->_windows; Window &w = windows[12]; Character &c = party._activeParty[_charIndex - 1]; if (!w._enabled) w.open(); w.writeString(Common::String::format(_message.c_str(), c._name.c_str())); w.update(); return true; } bool Scripts::cmdSignTextSml(ParamsIterator ¶ms) { Interface &intf = *_vm->_interface; intf._screenText = Common::String::format("\x2\f08\x3""c\t116\v090%s\x3l\fd\x1", _message.c_str()); intf._upDoorText = true; intf.draw3d(true); return true; } bool Scripts::cmdPlayEventVoc(ParamsIterator ¶ms) { Sound &sound = *_vm->_sound; sound.stopSound(); sound.playSound(Res.EVENT_SAMPLES[params.readByte()], 1); return true; } bool Scripts::cmdDisplayBottom(ParamsIterator ¶ms) { _windowIndex = 12; display(false, 0); return true; } bool Scripts::cmdIfMapFlag(ParamsIterator ¶ms) { Map &map = *_vm->_map; int monsterNum = params.readByte(); int lineNum = params.readByte(); if (monsterNum == 0xff) { for (monsterNum = 0; monsterNum < (int)map._mobData._monsters.size(); ++monsterNum) { MazeMonster &monster = map._mobData._monsters[monsterNum]; if ((uint)monster._position.x < 32 && (uint)monster._position.y < 32) return true; } } else { MazeMonster &monster = map._mobData._monsters[monsterNum]; if ((uint)monster._position.x < 32 && (uint)monster._position.y < 32) return true; } _lineNum = lineNum; return false; } bool Scripts::cmdSelectRandomChar(ParamsIterator ¶ms) { _charIndex = _vm->getRandomNumber(1, _vm->_party->_activeParty.size()); return true; } bool Scripts::cmdGiveEnchanted(ParamsIterator ¶ms) { Party &party = *_vm->_party; int itemOffset = _vm->getGameID() == GType_Swords ? 6 : 0; XeenItem *item; int invIndex; int id = params.readByte(); // Get category of item to add ItemCategory cat = CATEGORY_WEAPON; if (id < (35 + itemOffset)) { } else if (id < (49 + itemOffset)) { cat = CATEGORY_ARMOR; id -= 35 + itemOffset; } else if (id < (60 + itemOffset)) { cat = CATEGORY_ACCESSORY; id -= 49 + itemOffset; } else if (id < (82 + itemOffset)) { cat = CATEGORY_MISC; id -= 60 + itemOffset; } else { party._questItems[id - (82 + itemOffset)]++; } // Check for an empty slot for (invIndex = 0, item = party._treasure[cat]; invIndex < MAX_TREASURE_ITEMS && !item->empty(); ++invIndex, ++item) ; if (invIndex == MAX_TREASURE_ITEMS) { // Treasure category entirely full. Should never happen warning("Treasure category was completely filled up"); } else { party._treasure._hasItems = true; if (cat == CATEGORY_MISC) { // Handling of misc items. Note that for them, id actually specifies the material field item->_material = id; item->_id = params.readByte(); item->_state._counter = (item->_material == 10 || item->_material == 11) ? 1 : _vm->getRandomNumber(3, 10); } else { // Weapons, armor, and accessories item->_id = id; item->_material = params.readByte(); item->_state = params.readByte(); } } return true; } bool Scripts::cmdItemType(ParamsIterator ¶ms) { _itemType = params.readByte(); return true; } bool Scripts::cmdMakeNothingHere(ParamsIterator ¶ms) { Map &map = *_vm->_map; Party &party = *_vm->_party; // Scan through the event list and mark the opcodes for all the lines of any scripts // on the party's current cell as having no operation, effectively disabling them for (uint idx = 0; idx < map._events.size(); ++idx) { MazeEvent &evt = map._events[idx]; if (evt._position == party._mazePosition) evt._opcode = OP_None; } return cmdExit(params); } bool Scripts::cmdCheckProtection(ParamsIterator ¶ms) { if (copyProtectionCheck()) return true; else return cmdExit(params); } bool Scripts::cmdChooseNumeric(ParamsIterator ¶ms) { int choice = Choose123::show(_vm, params.readByte()); if (choice) { _lineNum = _event->_parameters[choice]; return false; } return true; } bool Scripts::cmdDisplayBottomTwoLines(ParamsIterator ¶ms) { Map &map = *_vm->_map; Windows &windows = *_vm->_windows; Window &w = windows[12]; params.readByte(); int textId = params.readByte(); Common::String msg = Common::String::format("\r\x03""c\t000\v007%s\n\n%s", "", map._events._text[textId].c_str()); w.close(); w.open(); w.writeString(msg); w.update(); YesNo::show(_vm, true); _lineNum = -1; return false; } bool Scripts::cmdDisplayLarge(ParamsIterator ¶ms) { Party &party = *g_vm->_party; Common::String filename = Common::String::format("aaze2%03u.txt", party._mazeId); uint offset = params.readByte(); // Get the text data for the current maze File f(filename); char *data = new char[f.size()]; f.read(data, f.size()); f.close(); // Get the message at the specified offset _message = Common::String(data + offset); delete[] data; // Display the message _windowIndex = 11; display(true, 0); return true; } bool Scripts::cmdExchObj(ParamsIterator ¶ms) { int id1 = params.readByte(), id2 = params.readByte(); MazeObject &obj1 = _vm->_map->_mobData._objects[id1]; MazeObject &obj2 = _vm->_map->_mobData._objects[id2]; Common::Point pt = obj1._position; obj1._position = obj2._position; obj2._position = pt; return true; } bool Scripts::cmdFallToMap(ParamsIterator ¶ms) { Interface &intf = *_vm->_interface; Party &party = *_vm->_party; party._fallMaze = params.readByte(); party._fallPosition.x = params.readShort(); party._fallPosition.y = params.readShort(); party._fallDamage = params.readByte(); intf.startFalling(true); _lineNum = SCRIPT_RESET; return false; } bool Scripts::cmdDisplayMain(ParamsIterator ¶ms) { _windowIndex = 11; display(false, 0); return true; } bool Scripts::cmdGoto(ParamsIterator ¶ms) { Map &map = *_vm->_map; map.getCell(1); if (map._currentSurfaceId == params.readByte()) { _lineNum = params.readByte(); return false; } return true; } bool Scripts::cmdGotoRandom(ParamsIterator ¶ms) { _lineNum = _event->_parameters[_vm->getRandomNumber(1, params.readByte())]; return false; } bool Scripts::cmdCutsceneEndDarkside(ParamsIterator ¶ms) { Party &party = *_vm->_party; _vm->_saves->_wonDarkSide = true; party._questItems[53] = 1; party._darkSideCompleted = true; party._mazeId = 29; party._mazeDirection = DIR_NORTH; party._mazePosition = Common::Point(25, 21); g_vm->_gameWon[1] = true; g_vm->_finalScore = party.getScore(); g_vm->saveSettings(); doDarkSideEnding(); return false; } bool Scripts::cmdCutsceneEndWorld(ParamsIterator ¶ms) { Party &party = *g_vm->_party; g_vm->_gameWon[2] = true; g_vm->_finalScore = party.getScore(); g_vm->saveSettings(); _vm->_saves->_wonWorld = true; _vm->_party->_worldCompleted = true; doWorldEnding(); return false; } bool Scripts::cmdFlipWorld(ParamsIterator ¶ms) { _vm->_map->_loadCcNum = params.readByte(); return true; } bool Scripts::cmdPlayCD(ParamsIterator ¶ms) { error("TODO"); } void Scripts::doCloudsEnding() { g_vm->_party->_cloudsCompleted = true; doEnding("ENDGAME"); g_vm->_mode = MODE_INTERACTIVE; g_vm->_saves->saveGame(); g_vm->_gameMode = GMODE_MENU; g_vm->_mode = MODE_STARTUP; } void Scripts::doDarkSideEnding() { g_vm->_party->_darkSideCompleted = true; doEnding("ENDGAME2"); } void Scripts::doWorldEnding() { doEnding("WORLDEND"); } void Scripts::doEnding(const Common::String &endStr) { Party &party = *_vm->_party; int state = 0; for (uint idx = 0; idx < party._activeParty.size(); ++idx) { Character &player = party._activeParty[idx]; if (player.hasAward(SUPER_GOOBER)) { state = 2; break; } else if (player.hasAward(GOOBER)) { state = 1; break; } } // Get the current total score uint finalScore = party.getScore(); g_vm->_mode = MODE_STARTUP; g_vm->showCutscene(endStr, state, finalScore); g_vm->_gameMode = GMODE_MENU; } bool Scripts::ifProc(int action, uint32 val, int mode, int charIndex) { FileManager &files = *_vm->_files; Party &party = *_vm->_party; Character *ps = (charIndex == -1) ? nullptr : &party._activeParty[charIndex]; uint v = 0; switch (action) { case 3: // Player sex v = (uint)ps->_sex; break; case 4: // Player race v = (uint)ps->_race; break; case 5: // Player class v = (uint)ps->_class; break; case 8: // Current health points v = (uint)ps->_currentHp; break; case 9: // Current spell points v = (uint)ps->_currentSp; break; case 10: // Get armor class v = (uint)ps->getArmorClass(false); break; case 11: // Level bonus (extra beyond base) v = ps->_level._temporary; break; case 12: // Current age, including unnatural aging v = ps->getAge(false); break; case 13: assert(val < 18); if (ps->_skills[val]) v = val; break; case 15: // Award assert(val < AWARDS_TOTAL); if (ps->hasAward(val)) v = val; break; case 16: // Experience v = ps->_experience; break; case 17: // Party poison resistence v = party._poisonResistence; break; case 18: // Condition assert(val < 16); if (!ps->_conditions[val] && !(val & 0x10)) v = val; break; case 19: { // Can player cast a given spell SpellsCategory category = ps->getSpellsCategory(); assert(category != SPELLCAT_INVALID); // Check if the character class can cast the particular spell for (int idx = 0; idx < SPELLS_PER_CLASS; ++idx) { if (Res.SPELLS_ALLOWED[category][idx] == (int)val) { // Can cast it. Check if the player has it in their spellbook if (ps->_spells[idx]) v = val; break; } } break; } case 20: assert(val < 256); if (files._ccNum && _vm->getGameID() != GType_Swords) val += 256; v = party._gameFlags[val / 256][val % 256] ? val : 0xffffffff; break; case 21: { // Scans inventories for given item number uint itemOffset = _vm->getGameID() == GType_Swords ? 6 : 0; v = 0xFFFFFFFF; if (val < (82 + itemOffset)) { for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) { if (val < (35 + itemOffset)) { if (ps->_weapons[idx]._id == val) { v = val; break; } } else if (val < (49 + itemOffset)) { if (ps->_armor[idx]._id == (val - 35)) { v = val; break; } } else if (val < (60 + itemOffset)) { if (ps->_accessories[idx]._id == (val - (49 + itemOffset))) { v = val; break; } } else { if (ps->_misc[idx]._id == (val - (60 + itemOffset))) { v = val; break; } } } } else if (party._questItems[val - (82 + itemOffset)]) { v = val; } break; } case 25: // Returns number of minutes elapsed in the day (0-1440) v = party._minutes; break; case 34: // Current party gold v = party._gold; break; case 35: // Current party gems v = party._gems; break; case 37: // Might bonus (extra beyond base) v = ps->_might._temporary; break; case 38: // Intellect bonus (extra beyond base) v = ps->_intellect._temporary; break; case 39: // Personality bonus (extra beyond base) v = ps->_personality._temporary; break; case 40: // Endurance bonus (extra beyond base) v = ps->_endurance._temporary; break; case 41: // Speed bonus (extra beyond base) v = ps->_speed._temporary; break; case 42: // Accuracy bonus (extra beyond base) v = ps->_accuracy._temporary; break; case 43: // Luck bonus (extra beyond base) v = ps->_luck._temporary; break; case 44: v = YesNo::show(_vm, val); v = (!v && !val) ? 2 : val; break; case 45: // Might base (before bonus) v = ps->_might._permanent; break; case 46: // Intellect base (before bonus) v = ps->_intellect._permanent; break; case 47: // Personality base (before bonus) v = ps->_personality._permanent; break; case 48: // Endurance base (before bonus) v = ps->_endurance._permanent; break; case 49: // Speed base (before bonus) v = ps->_speed._permanent; break; case 50: // Accuracy base (before bonus) v = ps->_accuracy._permanent; break; case 51: // Luck base (before bonus) v = ps->_luck._permanent; break; case 52: // Fire resistence (before bonus) v = ps->_fireResistence._permanent; break; case 53: // Elecricity resistence (before bonus) v = ps->_electricityResistence._permanent; break; case 54: // Cold resistence (before bonus) v = ps->_coldResistence._permanent; break; case 55: // Poison resistence (before bonus) v = ps->_poisonResistence._permanent; break; case 56: // Energy reistence (before bonus) v = ps->_energyResistence._permanent; break; case 57: // Energy resistence (before bonus) v = ps->_magicResistence._permanent; break; case 58: // Fire resistence (extra beyond base) v = ps->_fireResistence._temporary; break; case 59: // Electricity resistence (extra beyond base) v = ps->_electricityResistence._temporary; break; case 60: // Cold resistence (extra beyond base) v = ps->_coldResistence._temporary; break; case 61: // Poison resistence (extra beyod base) v = ps->_poisonResistence._temporary; break; case 62: // Energy resistence (extra beyond base) v = ps->_energyResistence._temporary; break; case 63: // Magic resistence (extra beyond base) v = ps->_magicResistence._temporary; break; case 64: // Level (before bonus) v = ps->_level._permanent; break; case 65: // Total party food v = party._food; break; case 69: v = party._levitateCount; break; case 70: // Amount of light v = party._lightCount; break; case 71: // Party magical fire resistence v = party._fireResistence; break; case 72: // Party magical electricity resistence v = party._electricityResistence; break; case 73: // Party magical cold resistence v = party._coldResistence; break; case 76: // Day of the year (100 per year) v = party._day; break; case 77: // Armor class (extra beyond base) v = ps->_ACTemp; break; case 78: // Test whether current Hp is equal to or exceeds the max HP v = ps->_currentHp >= ps->getMaxHP() ? 1 : 0; break; case 79: // Test for Wizard Eye being active v = party._wizardEyeActive ? 1 : 0; break; case 81: // Test whether current Sp is equal to or exceeds the max SP v = ps->_currentSp >= ps->getMaxSP() ? 1 : 0; break; case 84: // Current facing direction v = (uint)party._mazeDirection; break; case 85: // Current game year since start v = party._year; break; case 86: case 87: case 88: case 89: case 90: case 91: case 92: // Get a player stat v = ps->getStat((Attribute)(action - 86), 0); break; case 93: // Current day of the week (10 days per week) v = party._day / 10; break; case 94: // Test whether Walk on Water is currently active v = party._walkOnWaterActive ? 1 : 0; break; case 99: // Party skills check v = party.checkSkill((Skill)val) ? val : 0xffffffff; break; case 102: // Thievery skill v = ps->getThievery(); break; case 103: // Get value of world flag v = party._worldFlags[val] ? val : 0xffffffff; break; case 104: // Get value of quest flag v = party._questFlags[(_vm->getGameID() == GType_Swords ? 0 : files._ccNum * 30) + val] ? val : 0xffffffff; break; case 105: // Test number of Megacredits in party. Only used by King's Engineer in Castle Burlock v = party._questItems[26]; break; case 107: // Get value of character flag v = party._characterFlags[ps->_rosterId][val] ? val : 0xffffffff; break; default: break; } switch (mode) { case 0: return v >= val; case 1: return v == val; case 2: return v <= val; default: return false; } } bool Scripts::copyProtectionCheck() { // Only bother doing the protection check if it's been explicitly turned on if (!ConfMan.getBool("copy_protection")) return true; // Show the copy protection dialog return CopyProtection::show(_vm); } void Scripts::display(bool justifyFlag, int var46) { EventsManager &events = *_vm->_events; Interface &intf = *_vm->_interface; Windows &windows = *_vm->_windows; Window &w = windows[_windowIndex]; if (!_redrawDone) { intf.draw3d(true); _redrawDone = true; } windows[38].close(); if (!justifyFlag) _displayMessage = Common::String::format("\r\x3""c%s", _message.c_str()); if (!w._enabled) w.open(); while (!_vm->shouldExit()) { const char *newPos = w.writeString(_displayMessage); w.update(); // Check for end of message if (!newPos) break; _displayMessage = Common::String(newPos); if (_displayMessage.empty()) break; // Wait for click events.clearEvents(); do { events.updateGameCounter(); intf.draw3d(true); events.wait(1); } while (!_vm->shouldExit() && !events.isKeyMousePressed()); w.writeString(justifyFlag ? "\r" : "\r\x3""c"); } } } // End of namespace Xeen