mirror of
https://github.com/badvision/jace.git
synced 2024-06-15 09:29:36 +00:00
455 lines
18 KiB
Java
455 lines
18 KiB
Java
|
/*
|
||
|
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
||
|
*
|
||
|
* This library is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This library 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
|
||
|
* Lesser General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
* License along with this library; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||
|
* MA 02110-1301 USA
|
||
|
*/
|
||
|
package jace.cheat;
|
||
|
|
||
|
import jace.Emulator;
|
||
|
import jace.apple2e.RAM128k;
|
||
|
import jace.apple2e.SoftSwitches;
|
||
|
import jace.config.ConfigurableField;
|
||
|
import jace.core.Computer;
|
||
|
import jace.core.PagedMemory;
|
||
|
import jace.core.RAMEvent;
|
||
|
import jace.core.RAMListener;
|
||
|
import java.awt.Component;
|
||
|
import java.awt.MouseInfo;
|
||
|
import java.awt.Point;
|
||
|
import java.awt.event.MouseEvent;
|
||
|
import java.awt.event.MouseListener;
|
||
|
|
||
|
/**
|
||
|
* Prince of Persia game cheats. This would not have been possible without the
|
||
|
* source. I am eternally grateful to Jordan Mechner both for creating this
|
||
|
* game, and for being so kind to release the source code to it so that we can
|
||
|
* learn how it works. Where possible, I've indicated where I found the various
|
||
|
* game variables in the original source so that it might help anyone else
|
||
|
* trying to learn how this game works.
|
||
|
*
|
||
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
||
|
*/
|
||
|
public class PrinceOfPersiaCheats extends Cheats implements MouseListener {
|
||
|
|
||
|
@ConfigurableField(category = "Hack", name = "Feather fall", defaultValue = "false", description = "Fall like a feather!")
|
||
|
public static boolean velocityHack;
|
||
|
// Game memory locations
|
||
|
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/01%20POP%20Source/Source/GAMEEQ.S
|
||
|
@ConfigurableField(category = "Hack", name = "Invincibility", defaultValue = "false", description = "Warning: will crash game if you are impaled")
|
||
|
public static boolean invincibilityHack;
|
||
|
@ConfigurableField(category = "Hack", name = "Infinite Time", defaultValue = "false", description = "Freeze the clock")
|
||
|
public static boolean timeHack;
|
||
|
@ConfigurableField(category = "Hack", name = "Sleepy Time", defaultValue = "false", description = "Enemies won't react")
|
||
|
public static boolean sleepHack;
|
||
|
@ConfigurableField(category = "Hack", name = "Can haz sword?", defaultValue = "false", description = "Start with sword in level 1")
|
||
|
public static boolean swordHack;
|
||
|
@ConfigurableField(category = "Hack", name = "Mouse", defaultValue = "false", description = "Left click kills/opens, Right click teleports")
|
||
|
public static boolean mouseHack;
|
||
|
boolean mouseRegistered = false;
|
||
|
public static int PREV = 0x02b;
|
||
|
public static int SPREV = 0x02e;
|
||
|
public static int CharPosn = 0x040;
|
||
|
public static int CharX = 0x041;
|
||
|
public static int CharY = 0x042;
|
||
|
public static int CharFace = 0x043;
|
||
|
public static int CharBlockX = 0x44;
|
||
|
public static int CharBlockY = 0x45;
|
||
|
public static int CharAction = 0x46;
|
||
|
public static int CharXVel = 0x47;
|
||
|
public static int CharYVel = 0x48;
|
||
|
public static int CharSeq = 0x49; // Word
|
||
|
public static int CharScrn = 0x4b;
|
||
|
public static int CharRepeat = 0x4c;
|
||
|
public static int CharID = 0x4d;
|
||
|
public static int CharSword = 0x4e;
|
||
|
public static int CharLife = 0x4f;
|
||
|
public static int KidX = 0x051;
|
||
|
public static int KidY = 0x052;
|
||
|
public static int KidFace = 0x53;
|
||
|
public static int KidBlockX = 0x54;
|
||
|
public static int KidBlockY = 0x55;
|
||
|
public static int KidAction = 0x56;
|
||
|
public static int KidScrn = 0x5b;
|
||
|
public static int ShadBlockX = 0x64;
|
||
|
public static int ShadBlockY = 0x65;
|
||
|
public static int ShadLife = 0x06f;
|
||
|
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/02%20POP%20Disk%20Routines/CP.525/RYELLOW1.S
|
||
|
public static int deprotectCheckYellow = 0x07c;
|
||
|
public static int NumTrans = 0x096;
|
||
|
public static int OppStrength = 0x0cc;
|
||
|
public static int KidStrength = 0x0ce;
|
||
|
public static int EnemyAlert = 0x0d1;
|
||
|
public static int ChgOppStr = 0x0d2;
|
||
|
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/02%20POP%20Disk%20Routines/CP.525/PURPLE.MAIN.S
|
||
|
public static int deprotectCheckPurple = 0x0da;
|
||
|
public static int Heoric = 0x0d3;
|
||
|
public static int InEditor = 0x0202;
|
||
|
public static int MinLeft = 0x0300;
|
||
|
public static int hasSword = 0x030a;
|
||
|
public static int mobtables = 0x0b600;
|
||
|
public static int trloc = mobtables;
|
||
|
public static int trscrn = trloc + 0x020;
|
||
|
public static int trdirec = trscrn + 0x020;
|
||
|
// Blueprint (map level data)0
|
||
|
public static int BlueSpec = 0x0b9d0;
|
||
|
public static int LinkLoc = 0x0bca0;
|
||
|
public static int LinkMap = 0x0bda0;
|
||
|
public static int Map = 0x0bea0;
|
||
|
public static int MapInfo = 0x0bf00;
|
||
|
public static int RedBufs = 0x05e00;
|
||
|
public static int RedBuf = RedBufs + 90;
|
||
|
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/01%20POP%20Source/Source/EQ.S
|
||
|
public static int WipeBuf = RedBuf + 90;
|
||
|
public static int MoveBuf = WipeBuf + 30;
|
||
|
// Object types
|
||
|
// Source: https://github.com/jmechner/Prince-of-Persia-Apple-II/blob/master/01%20POP%20Source/Source/MOVEDATA.S
|
||
|
public static int space = 0;
|
||
|
public static int floor = 1;
|
||
|
public static int spikes = 2;
|
||
|
public static int posts = 3;
|
||
|
public static int gate = 4;
|
||
|
public static int dpressplate = 5;
|
||
|
public static int pressplate = 6;
|
||
|
public static int panelwif = 7;
|
||
|
public static int pillarbottom = 8;
|
||
|
public static int pillartop = 9;
|
||
|
public static int flask = 10;
|
||
|
public static int loose = 11;
|
||
|
public static int panelwof = 12;
|
||
|
public static int mirror = 13;
|
||
|
public static int rubble = 14;
|
||
|
public static int upressplate = 15;
|
||
|
public static int exit = 16;
|
||
|
public static int exit2 = 17;
|
||
|
public static int slicer = 18;
|
||
|
public static int torch = 19;
|
||
|
public static int block = 20;
|
||
|
public static int bones = 21;
|
||
|
public static int sword = 22;
|
||
|
public static int window = 23;
|
||
|
public static int window2 = 24;
|
||
|
public static int archbot = 25;
|
||
|
public static int archtop1 = 26;
|
||
|
public static int archtop2 = 27;
|
||
|
public static int archtop3 = 28;
|
||
|
public static int archtop4 = 29;
|
||
|
// This is the correct value for an open exit door.
|
||
|
public static int ExitOpen = 172;
|
||
|
|
||
|
@Override
|
||
|
protected String getDeviceName() {
|
||
|
return ("Prince of Persia");
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void tick() {
|
||
|
// Do nothing
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void attach() {
|
||
|
if (velocityHack) {
|
||
|
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||
|
@Override
|
||
|
protected void doConfig() {
|
||
|
setScopeStart(CharYVel);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void doEvent(RAMEvent e) {
|
||
|
registerMouse();
|
||
|
if (!SoftSwitches.AUXZP.getState()) {
|
||
|
return;
|
||
|
}
|
||
|
int newVel = e.getNewValue();
|
||
|
if (newVel > 5) {
|
||
|
newVel = 1;
|
||
|
}
|
||
|
e.setNewValue(newVel & 0x0ff);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (invincibilityHack) {
|
||
|
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||
|
@Override
|
||
|
protected void doConfig() {
|
||
|
setScopeStart(KidStrength);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void doEvent(RAMEvent e) {
|
||
|
registerMouse();
|
||
|
if (!SoftSwitches.AUXZP.getState()) {
|
||
|
return;
|
||
|
}
|
||
|
e.setNewValue(3);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (sleepHack) {
|
||
|
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||
|
@Override
|
||
|
protected void doConfig() {
|
||
|
setScopeStart(EnemyAlert);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void doEvent(RAMEvent e) {
|
||
|
registerMouse();
|
||
|
if (!SoftSwitches.AUXZP.getState()) {
|
||
|
return;
|
||
|
}
|
||
|
e.setNewValue(0);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (swordHack) {
|
||
|
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||
|
@Override
|
||
|
protected void doConfig() {
|
||
|
setScopeStart(hasSword);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void doEvent(RAMEvent e) {
|
||
|
registerMouse();
|
||
|
if (!SoftSwitches.AUXZP.getState()) {
|
||
|
return;
|
||
|
}
|
||
|
e.setNewValue(1);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (timeHack) {
|
||
|
addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
|
||
|
@Override
|
||
|
protected void doConfig() {
|
||
|
setScopeStart(MinLeft);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void doEvent(RAMEvent e) {
|
||
|
registerMouse();
|
||
|
if (!SoftSwitches.AUXZP.getState()) {
|
||
|
return;
|
||
|
}
|
||
|
e.setNewValue(0x069);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (mouseHack) {
|
||
|
if (Emulator.getScreen() != null) {
|
||
|
Emulator.getScreen().addMouseListener(this);
|
||
|
} else {
|
||
|
mouseRegistered = true;
|
||
|
}
|
||
|
} else {
|
||
|
if (Emulator.getScreen() != null) {
|
||
|
Emulator.getScreen().removeMouseListener(this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void detach() {
|
||
|
super.detach();
|
||
|
if (Emulator.getScreen() != null) {
|
||
|
Emulator.getScreen().removeMouseListener(this);
|
||
|
}
|
||
|
mouseRegistered = false;
|
||
|
}
|
||
|
public static int BlueType = 0x0b700;
|
||
|
|
||
|
public void registerMouse() {
|
||
|
if (mouseRegistered) {
|
||
|
Component drawingArea = Emulator.getScreen();
|
||
|
if (drawingArea == null) {
|
||
|
return;
|
||
|
}
|
||
|
Emulator.getScreen().addMouseListener(this);
|
||
|
mouseRegistered = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void mouseClicked(MouseEvent me) {
|
||
|
Component drawingArea = Emulator.getScreen();
|
||
|
if (drawingArea == null) {
|
||
|
return;
|
||
|
}
|
||
|
Point currentMouseLocation = MouseInfo.getPointerInfo().getLocation();
|
||
|
Point topLeft = drawingArea.getLocationOnScreen();
|
||
|
Double x = (currentMouseLocation.x - topLeft.x) / drawingArea.getSize().getWidth();
|
||
|
// Offset y by three pixels to account for tiles above
|
||
|
Double y = (currentMouseLocation.y - topLeft.y) / drawingArea.getSize().getHeight() - 0.015625;
|
||
|
// Now we have the x and y coordinates ranging from 0 to 1.0, scale to POP values
|
||
|
int row = y < 0 ? -1 : (int) (y * 3);
|
||
|
int col = (int) (x * 10);
|
||
|
|
||
|
// Do a check if we are at the bottom of the tile, the user might have been clicking on the tile to the right.
|
||
|
// This accounts for the isometric view and allows a little more flexibility, not to mention warping behind gates
|
||
|
// that are on the left edge of the screen!
|
||
|
int yCoor = ((int) (y * 192) % 63);
|
||
|
if (yCoor >= 47) {
|
||
|
double yOffset = 1.0 - (((double) yCoor - 47.0) / 16.0);
|
||
|
int xCoor = ((int) (x * 280) % 28);
|
||
|
double xOffset = ((double) xCoor) / 28.0;
|
||
|
if (xOffset <= yOffset) {
|
||
|
col--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Note: POP uses a 255-pixel horizontal axis, Pixels 0-57 are offscreen to the left
|
||
|
// and 198-255 offscreen to the right.
|
||
|
|
||
|
// System.out.println("Clicked on " + col + "," + row + " -- screen " + (x * 280) + "," + (y * 192));
|
||
|
RAM128k mem = (RAM128k) Computer.getComputer().getMemory();
|
||
|
PagedMemory auxMem = mem.getAuxMemory();
|
||
|
|
||
|
if (me.getButton() == MouseEvent.BUTTON1) {
|
||
|
// Left click hacks
|
||
|
// See if there is an opponent we can kill off.
|
||
|
int opponentX = auxMem.readByte(ShadBlockX);
|
||
|
int opponentY = auxMem.readByte(ShadBlockY);
|
||
|
int opponentLife = auxMem.readByte(ShadLife);
|
||
|
// If there is a guy near where the user clicked and he's alive, then kill 'em.
|
||
|
if (opponentLife != 0 && opponentY == row && Math.abs(col - opponentX) <= 1) {
|
||
|
// System.out.println("Enemy at " + opponentX + "," + opponentY + "; life=" + opponentLife);
|
||
|
// Occasionally, if the code is at the right spot this will cause the special effect of a hit to appear
|
||
|
auxMem.writeByte(ChgOppStr, (byte) -opponentLife);
|
||
|
// And this will kill the dude pretty much right away.
|
||
|
auxMem.writeByte(ShadLife, (byte) 0);
|
||
|
} else if (row >= 0 && col >= 0) {
|
||
|
// Try to perform actions on the block clicked as well as to the left and right of it.
|
||
|
// This opens gates and exits.
|
||
|
performAction(row, col, 1);
|
||
|
performAction(row, col - 1, 1);
|
||
|
performAction(row, col + 1, 1);
|
||
|
}
|
||
|
} else {
|
||
|
// Right/middle click == warp
|
||
|
byte warpX = (byte) (x * 140 + 58);
|
||
|
// This aliases the Y coordinate so the prince is on the floor at the correct spot.
|
||
|
byte warpY = (byte) ((row * 63) + 54);
|
||
|
// System.out.println("Warping to " + warpX + "," + warpY);
|
||
|
auxMem.writeByte(KidX, warpX);
|
||
|
auxMem.writeByte(KidY, warpY);
|
||
|
auxMem.writeByte(KidBlockX, (byte) col);
|
||
|
auxMem.writeByte(KidBlockY, (byte) row);
|
||
|
// Set action to bump into a wall so it can reset the kid's feet on the ground correctly.
|
||
|
// Not sure if this has any real effect but things seem to be working (so I'll just leave this here...)
|
||
|
auxMem.writeByte(KidAction, (byte) 5);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param row
|
||
|
* @param col
|
||
|
* @param direction
|
||
|
*/
|
||
|
public void performAction(int row, int col, int direction) {
|
||
|
RAM128k mem = (RAM128k) Computer.getComputer().getMemory();
|
||
|
PagedMemory auxMem = mem.getAuxMemory();
|
||
|
byte currentScrn = auxMem.readByte(KidScrn);
|
||
|
if (col < 0) {
|
||
|
col += 10;
|
||
|
int scrnLeft = auxMem.readByte(Map + ((currentScrn - 1) * 4));
|
||
|
if (scrnLeft == 0) {
|
||
|
return;
|
||
|
}
|
||
|
currentScrn = (byte) scrnLeft;
|
||
|
byte prev = auxMem.readByte(PREV + row);
|
||
|
byte sprev = auxMem.readByte(SPREV + row);
|
||
|
// If the block to the left is gate, let's lie about it being open... for science
|
||
|
// This causes odd-looking screen behavior but it gets the job done.
|
||
|
if (prev == 4) {
|
||
|
// Update the temp variable that represents that object
|
||
|
auxMem.writeByte(SPREV + row, (byte) 255);
|
||
|
// And also update the blueprint
|
||
|
auxMem.writeByte(BlueSpec + ((scrnLeft - 1) * 30) + row * 10 + 9, (byte) 255);
|
||
|
}
|
||
|
// System.out.println("Looking at room to left, row "+row+": "+Integer.toHexString(prev)+","+Integer.toHexString(sprev));
|
||
|
} else if (col >= 10) {
|
||
|
// This code will probably never be called but here just in case.
|
||
|
col -= 10;
|
||
|
int scrnRight = auxMem.readByte(Map + ((currentScrn - 1) * 4) + 1);
|
||
|
if (scrnRight == 0) {
|
||
|
return;
|
||
|
}
|
||
|
currentScrn = (byte) scrnRight;
|
||
|
}
|
||
|
int numTransition = auxMem.readByte(NumTrans);
|
||
|
byte clickedLoc = (byte) (row * 10 + col);
|
||
|
// Figure out what kind of block is there
|
||
|
int blockType = auxMem.readByte(BlueType + (currentScrn - 1) * 30 + row * 10 + col) & 0x01f;
|
||
|
if (blockType == exit2 || blockType == exit) {
|
||
|
// Open the exit by changing the map data and adding the tiles to the move buffer
|
||
|
auxMem.writeByte(BlueSpec + (currentScrn - 1) * 30 + row * 10 + col, (byte) ExitOpen);
|
||
|
direction = 1;
|
||
|
// Tell the graphics engine that this piece has moved.
|
||
|
auxMem.writeByte(MoveBuf + row * 10 + col, (byte) 2);
|
||
|
}
|
||
|
if (blockType == gate || blockType == exit2 || blockType == exit) {
|
||
|
// If the object in question can be opened (exit or gate) add it to the transitional animation buffer
|
||
|
//System.out.print("Triggering screen " + currentScrn + " at pos " + clickedLoc);
|
||
|
boolean addTransition = false;
|
||
|
if (numTransition == 0) {
|
||
|
addTransition = true;
|
||
|
} else {
|
||
|
addTransition = true;
|
||
|
for (int i = 1; i <= numTransition; i++) {
|
||
|
byte scrn = auxMem.readByte(trscrn + i);
|
||
|
byte loc = auxMem.readByte(trloc + i);
|
||
|
if (scrn == currentScrn && loc == clickedLoc) {
|
||
|
// Entry already exists, just change its direction
|
||
|
auxMem.writeByte(trdirec + i, (byte) direction);
|
||
|
addTransition = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (addTransition && numTransition >= 0x20) {
|
||
|
addTransition = false;
|
||
|
}
|
||
|
}
|
||
|
// If the object was not in the animation buffer, add it.
|
||
|
if (addTransition) {
|
||
|
numTransition++;
|
||
|
auxMem.writeByte(trdirec + numTransition, (byte) direction);
|
||
|
auxMem.writeByte(trscrn + numTransition, currentScrn);
|
||
|
auxMem.writeByte(trloc + numTransition, clickedLoc);
|
||
|
auxMem.writeByte(NumTrans, (byte) numTransition);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void mousePressed(MouseEvent me) {
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void mouseReleased(MouseEvent me) {
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void mouseEntered(MouseEvent me) {
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void mouseExited(MouseEvent me) {
|
||
|
}
|
||
|
}
|