/* * $Id: MachineImpl.java 536 2008-02-19 06:03:27Z weiju $ * * Created on 2005/10/03 * Copyright 2005-2008 by Wei-ju Wu * This file is part of The Z-machine Preservation Project (ZMPP). * * ZMPP 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. * * ZMPP 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 ZMPP. If not, see <http://www.gnu.org/licenses/>. */ package org.zmpp.vm; import org.zmpp.blorb.BlorbImage; import org.zmpp.encoding.ZsciiString; import org.zmpp.iff.FormChunk; import org.zmpp.iff.WritableFormChunk; import org.zmpp.media.MediaCollection; import org.zmpp.media.PictureManager; import org.zmpp.media.PictureManagerImpl; import org.zmpp.media.SoundEffect; import org.zmpp.media.SoundSystem; import org.zmpp.media.SoundSystemImpl; import org.zmpp.vm.StoryFileHeader.Attribute; import org.zmpp.vmutil.PredictableRandomGenerator; import org.zmpp.vmutil.RandomGenerator; import org.zmpp.vmutil.RingBuffer; import org.zmpp.vmutil.UnpredictableRandomGenerator; /** * This class implements the state and some services of a Z-machine, version 3. * * @author Wei-ju Wu * @version 1.0 */ public class MachineImpl implements Machine { /** * Number of undo steps. */ private static final int NUM_UNDO = 5; private GameData gamedata; private RandomGenerator random; private StatusLine statusLine; private ScreenModel screenModel; private SaveGameDataStore datastore; private RingBuffer<PortableGameState> undostates; private InputFunctions inputFunctions; private SoundSystem soundSystem; private PictureManager pictureManager; private Cpu cpu; private Output output; private Input input; /** * Constructor. */ public MachineImpl() { this.inputFunctions = new InputFunctions(this); } /** * {@inheritDoc} */ public int getVersion() { return gamedata.getStoryFileHeader().getVersion(); } /** * {@inheritDoc} */ public GameData getGameData() { return gamedata; } /** * {@inheritDoc} */ public Cpu getCpu() { return cpu; } /** * {@inheritDoc} */ public Output getOutput() { return output; } /** * {@inheritDoc} */ public Input getInput() { return input; } /** * {@inheritDoc} */ public void initialize(final GameData gamedata, final InstructionDecoder decoder) { this.gamedata = gamedata; this.random = new UnpredictableRandomGenerator(); this.undostates = new RingBuffer<PortableGameState>(NUM_UNDO); cpu = new CpuImpl(this, decoder); output = new OutputImpl(gamedata, cpu); input = new InputImpl(this); MediaCollection<SoundEffect> sounds = null; MediaCollection<BlorbImage> pictures = null; int resourceRelease = 0; if (gamedata.getResources() != null) { sounds = gamedata.getResources().getSounds(); pictures = gamedata.getResources().getImages(); resourceRelease = gamedata.getResources().getRelease(); } soundSystem = new SoundSystemImpl(sounds); pictureManager = new PictureManagerImpl(resourceRelease, this, pictures); resetState(); } /** * {@inheritDoc} */ public short random(final short range) { if (range < 0) { random = new PredictableRandomGenerator(-range); return 0; } else if (range == 0) { random = new UnpredictableRandomGenerator(); return 0; } return (short) ((random.next() % range) + 1); } // ************************************************************************ // ****** Control functions // ************************************************ /** * {@inheritDoc} */ public void warn(final String msg) { System.err.println("WARNING: " + msg); } /** * {@inheritDoc} */ public void restart() { restart(true); } /** * {@inheritDoc} */ public void quit() { cpu.setRunning(false); // On quit, close the streams output.print(new ZsciiString("*Game ended*")); closeStreams(); screenModel.redraw(); } /** * {@inheritDoc} */ public void start() { cpu.setRunning(true); } // ************************************************************************ // ****** Machine services // ************************************************ /** * {@inheritDoc} */ public void tokenize(final int textbuffer, final int parsebuffer, final int dictionaryAddress, final boolean flag) { inputFunctions.tokenize(textbuffer, parsebuffer, dictionaryAddress, flag); } /** * {@inheritDoc} */ public char readLine(final int textbuffer, final int time, final int routineAddress) { return inputFunctions.readLine(textbuffer, time, routineAddress); } /** * {@inheritDoc} */ public char readChar(final int time, final int routineAddress) { return inputFunctions.readChar(time, routineAddress); } /** * {@inheritDoc} */ public SoundSystem getSoundSystem() { return soundSystem; } /** * {@inheritDoc} */ public PictureManager getPictureManager() { return pictureManager; } /** * {@inheritDoc} */ public void setSaveGameDataStore(final SaveGameDataStore datastore) { this.datastore = datastore; } /** * {@inheritDoc} */ public void updateStatusLine() { if (gamedata.getStoryFileHeader().getVersion() <= 3 && statusLine != null) { final int objNum = cpu.getVariable(0x10); final String objectName = gamedata.getZCharDecoder().decode2Zscii( gamedata.getMemory(), gamedata.getObjectTree().getPropertiesDescriptionAddress(objNum), 0) .toString(); final int global2 = cpu.getVariable(0x11); final int global3 = cpu.getVariable(0x12); if (gamedata.getStoryFileHeader().isEnabled(Attribute.SCORE_GAME)) { statusLine.updateStatusScore(objectName, global2, global3); } else { statusLine.updateStatusTime(objectName, global2, global3); } } } /** * {@inheritDoc} */ public void setStatusLine(final StatusLine statusLine) { this.statusLine = statusLine; } /** * {@inheritDoc} */ public void setScreen(final ScreenModel screen) { this.screenModel = screen; } /** * {@inheritDoc} */ public ScreenModel getScreen() { return screenModel; } /** * {@inheritDoc} */ public ScreenModel6 getScreen6() { return (ScreenModel6) screenModel; } /** * {@inheritDoc} */ public boolean save(final int savepc) { if (datastore != null) { final PortableGameState gamestate = new PortableGameState(); gamestate.captureMachineState(this, savepc); final WritableFormChunk formChunk = gamestate.exportToFormChunk(); return datastore.saveFormChunk(formChunk); } return false; } /** * {@inheritDoc} */ public boolean save_undo(final int savepc) { final PortableGameState undoGameState = new PortableGameState(); undoGameState.captureMachineState(this, savepc); undostates.add(undoGameState); return true; } /** * {@inheritDoc} */ public PortableGameState restore() { if (datastore != null) { final PortableGameState gamestate = new PortableGameState(); final FormChunk formchunk = datastore.retrieveFormChunk(); gamestate.readSaveGame(formchunk); // verification has to be here if (verifySaveGame(gamestate)) { // do not reset screen model, since e.g. AMFV simply picks up the // current window state restart(false); gamestate.transferStateToMachine(this); //System.out.printf("restore(), pc is: %4x running: %b\n", getProgramCounter(), isRunning()); return gamestate; } } return null; } /** * {@inheritDoc} */ public PortableGameState restore_undo() { // do not reset screen model, since e.g. AMFV simply picks up the // current window state if (undostates.size() > 0) { final PortableGameState undoGameState = undostates.remove(undostates.size() - 1); restart(false); undoGameState.transferStateToMachine(this); System.out.printf("restore(), pc is: %4x\n", cpu.getProgramCounter()); return undoGameState; } return null; } // *********************************************************************** // ***** Private methods // ************************************** private boolean verifySaveGame(final PortableGameState gamestate) { // Verify the game according to the standard final StoryFileHeader fileHeader = gamedata.getStoryFileHeader(); int checksum = fileHeader.getChecksum(); if (checksum == 0) { checksum = gamedata.getCalculatedChecksum(); } return gamestate.getRelease() == fileHeader.getRelease() && gamestate.getChecksum() == checksum && gamestate.getSerialNumber().equals(fileHeader.getSerialNumber()); } /** * Close the streams. */ private void closeStreams() { input.close(); output.close(); } /** * Resets all state to initial values, using the configuration object. */ private void resetState() { output.reset(); soundSystem.reset(); cpu.reset(); //gamedata.getStoryFileHeader().setStandardRevision(1, 0); if (gamedata.getStoryFileHeader().getVersion() >= 4) { gamedata.getStoryFileHeader().setEnabled(Attribute.SUPPORTS_TIMED_INPUT, true); //gamedata.getStoryFileHeader().setInterpreterNumber(4); // Amiga gamedata.getStoryFileHeader().setInterpreterNumber(6); // IBM PC gamedata.getStoryFileHeader().setInterpreterVersion(1); } } private void restart(final boolean resetScreenModel) { // Transcripting and fixed font bits survive the restart final StoryFileHeader fileHeader = gamedata.getStoryFileHeader(); final boolean fixedFontForced = fileHeader.isEnabled(Attribute.FORCE_FIXED_FONT); final boolean transcripting = fileHeader.isEnabled(Attribute.TRANSCRIPTING); gamedata.reset(); resetState(); if (resetScreenModel) { screenModel.reset(); } fileHeader.setEnabled(Attribute.TRANSCRIPTING, transcripting); fileHeader.setEnabled(Attribute.FORCE_FIXED_FONT, fixedFontForced); } // *********************************************************************** // ***** Object accesss // ************************************ private ObjectTree getObjectTree() { return gamedata.getObjectTree(); } /** * {@inheritDoc} */ public void insertObject(int parentNum, int objectNum) { getObjectTree().insertObject(parentNum, objectNum); } /** * {@inheritDoc} */ public void removeObject(int objectNum) { getObjectTree().removeObject(objectNum); } /** * {@inheritDoc} */ public void clearAttribute(int objectNum, int attributeNum) { getObjectTree().clearAttribute(objectNum, attributeNum); } /** * {@inheritDoc} */ public boolean isAttributeSet(int objectNum, int attributeNum) { return getObjectTree().isAttributeSet(objectNum, attributeNum); } /** * {@inheritDoc} */ public void setAttribute(int objectNum, int attributeNum) { getObjectTree().setAttribute(objectNum, attributeNum); } /** * {@inheritDoc} */ public int getParent(int objectNum) { return getObjectTree().getParent(objectNum); } /** * {@inheritDoc} */ public void setParent(int objectNum, int parent) { getObjectTree().setParent(objectNum, parent); } /** * {@inheritDoc} */ public int getChild(int objectNum) { return getObjectTree().getChild(objectNum); } /** * {@inheritDoc} */ public void setChild(int objectNum, int child) { getObjectTree().setChild(objectNum, child); } /** * {@inheritDoc} */ public int getSibling(int objectNum) { return getObjectTree().getSibling(objectNum); } /** * {@inheritDoc} */ public void setSibling(int objectNum, int sibling) { getObjectTree().setSibling(objectNum, sibling); } /** * {@inheritDoc} */ public int getPropertiesDescriptionAddress(int objectNum) { return getObjectTree().getPropertiesDescriptionAddress(objectNum); } /** * {@inheritDoc} */ public int getPropertyAddress(int objectNum, int property) { return getObjectTree().getPropertyAddress(objectNum, property); } /** * {@inheritDoc} */ public int getPropertyLength(int propertyAddress) { return getObjectTree().getPropertyLength(propertyAddress); } /** * {@inheritDoc} */ public int getProperty(int objectNum, int property) { return getObjectTree().getProperty(objectNum, property); } /** * {@inheritDoc} */ public void setProperty(int objectNum, int property, int value) { getObjectTree().setProperty(objectNum, property, value); } /** * {@inheritDoc} */ public int getNextProperty(int objectNum, int property) { return getObjectTree().getNextProperty(objectNum, property); } }