Code cleanup using Lambda expressions -- Cheats are now easier to write.

This commit is contained in:
Brendan Robert 2015-08-15 14:34:12 -05:00
parent 73987753ba
commit 9985f096e4
6 changed files with 185 additions and 268 deletions

View File

@ -18,57 +18,94 @@
*/
package jace.cheat;
import jace.apple2e.MOS65C02;
import jace.apple2e.SoftSwitches;
import jace.core.Computer;
import jace.core.Device;
import jace.core.RAMEvent;
import jace.core.RAMListener;
import java.util.HashSet;
import java.util.Set;
/**
* Represents some combination of hacks that can be enabled or disabled
* through the configuration interface.
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
* Represents some combination of hacks that can be enabled or disabled through
* the configuration interface.
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public abstract class Cheats extends Device {
Set<RAMListener> listeners = new HashSet<>();
public Cheats(Computer computer) {
super(computer);
}
public void bypassCode(int address, int addressEnd) {
addCheat(RAMEvent.TYPE.READ, address, addressEnd, (e) -> {
e.setNewValue(MOS65C02.COMMAND.NOP.ordinal());
});
}
public void forceValue(int address, int value) {
addCheat(RAMEvent.TYPE.READ, address, (e) -> {
e.setNewValue(value);
});
}
public void addCheat(RAMListener l) {
public void forceValue(int address, boolean auxFlag, int value) {
addCheat(RAMEvent.TYPE.READ, address, (e) -> {
if (address < 0x0100) {
if (SoftSwitches.AUXZP.getState() != auxFlag) {
return;
}
} else {
if (SoftSwitches.RAMRD.getState() != auxFlag) {
return;
}
}
e.setNewValue(value);
});
}
public void addCheat(RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
RAMListener l = computer.getMemory().observe(type, address, handler);
listeners.add(l);
}
public void addCheat(RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) {
RAMListener l = computer.getMemory().observe(type, addressStart, addressEnd, handler);
listeners.add(l);
computer.getMemory().addListener(l);
}
@Override
public void attach() {
registerListeners();
}
@Override
public void detach() {
unregisterListeners();
super.detach();
super.detach();
}
abstract void registerListeners();
protected void unregisterListeners() {
listeners.stream().forEach((l) -> {
computer.getMemory().removeListener(l);
});
listeners.clear();
listeners.clear();
}
@Override
public void reconfigure() {
unregisterListeners();
registerListeners();
}
@Override
public String getShortName() {
return "cheat";
}
}
}

View File

@ -5,9 +5,6 @@ import jace.apple2e.MOS65C02;
import jace.config.ConfigurableField;
import jace.core.Computer;
import jace.core.RAMEvent;
import jace.core.RAMListener;
import java.util.HashMap;
import java.util.Map;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.input.MouseButton;
@ -16,25 +13,25 @@ public class MontezumasRevengeCheats extends Cheats {
@ConfigurableField(category = "Hack", name = "Repulsive", defaultValue = "false", description = "YOU STINK!")
public static boolean repulsiveHack = false;
@ConfigurableField(category = "Hack", name = "Feather Fall", defaultValue = "false", description = "Falling will not result in death")
public static boolean featherFall = false;
@ConfigurableField(category = "Hack", name = "Moon Jump", defaultValue = "false", description = "Wheeee!")
public static boolean moonJump = false;
@ConfigurableField(category = "Hack", name = "Infinite Lives", defaultValue = "false", description = "Game on!")
public static boolean infiniteLives = false;
public static boolean infiniteLives = false;
@ConfigurableField(category = "Hack", name = "Score hack", defaultValue = "false", description = "Change the score")
public static boolean scoreHack = false;
public static boolean scoreHack = false;
@ConfigurableField(category = "Hack", name = "Snake Charmer", defaultValue = "false", description = "Disable collision detection with enemies")
public static boolean snakeCharmer = false;
@ConfigurableField(category = "Hack", name = "Teleport", defaultValue = "false", description = "Click to teleport!")
public static boolean mouseHack = false;
public static int X_MAX = 80;
public static int Y_MAX = 160;
public static int MAX_VEL = 4;
@ -48,6 +45,8 @@ public class MontezumasRevengeCheats extends Cheats {
public static int Y_VELOCITY = 0x01550;
public static int CHAR_STATE = 0x01570;
public static int lastX = 0;
public MontezumasRevengeCheats(Computer computer) {
super(computer);
}
@ -62,152 +61,37 @@ public class MontezumasRevengeCheats extends Cheats {
mouseClicked(event.getButton());
}
};
@Override
void registerListeners() {
if (repulsiveHack) {
addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x1508);
setScopeEnd(0x1518);
}
int lastX = 0;
@Override
protected void doEvent(RAMEvent e) {
int playerX = computer.getMemory().readRaw(PLAYER_X);
int playerY = computer.getMemory().readRaw(PLAYER_Y);
for (int num = 7; num > 0; num--) {
int monsterX = computer.getMemory().readRaw(PLAYER_X + num);
int monsterY = computer.getMemory().readRaw(PLAYER_Y + num);
if (monsterX != 0 && monsterY != 0) {
if (Math.abs(monsterY - playerY) < 19) {
if (Math.abs(monsterX - playerX) < 7) {
int movement = Math.max(1, Math.abs(lastX - playerX));
if (monsterX > playerX) {
monsterX += movement;
} else {
monsterX -= movement;
if (monsterX <= 0) {
monsterX = 80;
}
}
computer.getMemory().write(PLAYER_X + num, (byte) monsterX, false, false);
}
}
}
}
lastX = playerX;
}
});
addCheat(RAMEvent.TYPE.WRITE, 0x1508, 0x1518, this::repulsiveBehavior);
}
if (featherFall) {
addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(PLAYER_Y);
}
@Override
protected void doEvent(RAMEvent e) {
if (e.getNewValue() != e.getOldValue()) {
int yVel = computer.getMemory().readRaw(Y_VELOCITY);
if (yVel > MAX_VEL) {
computer.getMemory().write(Y_VELOCITY, (byte) MAX_VEL, false, false);
}
}
}
});
addCheat(new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x6bb3);
setScopeEnd(0x6bb4);
}
@Override
protected void doEvent(RAMEvent e) {
e.setNewValue(MOS65C02.COMMAND.NOP.ordinal());
}
});
addCheat(RAMEvent.TYPE.WRITE, PLAYER_Y, this::featherFallBehavior);
// Bypass the part that realizes you should die when you hit the floor
bypassCode(0x6bb3, 0x6bb4);
}
if (moonJump) {
addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(Y_VELOCITY);
}
@Override
protected void doEvent(RAMEvent e) {
if (inStartingSequence()) return;
// System.out.println(e.getNewValue());
if (e.getNewValue() < 0 && e.getNewValue() < e.getOldValue()) {
e.setNewValue(MOON_JUMP_VELOCITY);
}
}
});
addCheat(RAMEvent.TYPE.WRITE, Y_VELOCITY, this::moonJumpBehavior);
}
if (infiniteLives) {
addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(LIVES);
}
@Override
protected void doEvent(RAMEvent e) {
e.setNewValue(11);
}
});
if (infiniteLives) {
forceValue(LIVES, 11);
}
if (scoreHack) {
addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(SCORE);
setScopeEnd(SCORE_END);
}
@Override
protected void doEvent(RAMEvent e) {
switch(e.getAddress()) {
case 0x0e8:
e.setNewValue(0x90);
break;
case 0x0e9:
e.setNewValue(0x09);
break;
case 0x0ea:
e.setNewValue(0x13);
break;
}
}
});
// Score: 900913
forceValue(SCORE, 0x90);
forceValue(SCORE + 1, 0x09);
forceValue(SCORE + 2, 0x13);
}
if (snakeCharmer) {
addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(0x07963);
setScopeEnd(0x07964);
}
@Override
protected void doEvent(RAMEvent e) {
e.setNewValue(MOS65C02.COMMAND.NOP.ordinal());
}
});
if (snakeCharmer) {
// Skip the code that determines you're touching an enemy
bypassCode(0x07963, 0x07964);
}
if (mouseHack) {
EmulatorUILogic.addMouseListener(listener);
@ -216,28 +100,66 @@ public class MontezumasRevengeCheats extends Cheats {
}
}
private void repulsiveBehavior(RAMEvent e) {
int playerX = computer.getMemory().readRaw(PLAYER_X);
int playerY = computer.getMemory().readRaw(PLAYER_Y);
for (int num = 7; num > 0; num--) {
int monsterX = computer.getMemory().readRaw(PLAYER_X + num);
int monsterY = computer.getMemory().readRaw(PLAYER_Y + num);
if (monsterX != 0 && monsterY != 0) {
if (Math.abs(monsterY - playerY) < 19) {
if (Math.abs(monsterX - playerX) < 7) {
int movement = Math.max(1, Math.abs(lastX - playerX));
if (monsterX > playerX) {
monsterX += movement;
} else {
monsterX -= movement;
if (monsterX <= 0) {
monsterX = 80;
}
}
computer.getMemory().write(PLAYER_X + num, (byte) monsterX, false, false);
}
}
}
}
lastX = playerX;
}
private void featherFallBehavior(RAMEvent yCoordChangeEvent) {
if (yCoordChangeEvent.getNewValue() != yCoordChangeEvent.getOldValue()) {
int yVel = computer.getMemory().readRaw(Y_VELOCITY);
if (yVel > MAX_VEL) {
computer.getMemory().write(Y_VELOCITY, (byte) MAX_VEL, false, false);
}
}
}
private void moonJumpBehavior(RAMEvent velocityChangeEvent) {
if (inStartingSequence()) {
return;
}
if (velocityChangeEvent.getNewValue() < 0
&& velocityChangeEvent.getNewValue() < velocityChangeEvent.getOldValue()) {
velocityChangeEvent.setNewValue(MOON_JUMP_VELOCITY);
}
}
private boolean inStartingSequence() {
int roomLevel = computer.getMemory().readRaw(ROOM_LEVEL);
return roomLevel == -1;
}
@Override
public String getName() {
return "Montezuma's Revenge";
}
@Override
protected String getDeviceName() {
return "Montezuma's Revenge";
}
Map<Integer, Integer> oldValues = new HashMap<>();
private void monitor(int start, int end) {
for (int i=start; i < end; i++) {
int old = oldValues.get(i) == null ? 0 : oldValues.get(i);
int val = computer.getMemory().readRaw(i) & 0x0ff;
if (old == val) continue;
oldValues.put(i, val);
System.out.println(Integer.toHexString(i) + ":" + val+";"+old+" ("+(val-old)+")");
}
System.out.println("-------");
}
@Override
public void tick() {
}

View File

@ -25,20 +25,19 @@ import jace.config.ConfigurableField;
import jace.core.Computer;
import jace.core.PagedMemory;
import jace.core.RAMEvent;
import jace.core.RAMListener;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.input.MouseButton;
/**
* Prince of Persia game cheats. This would not have been possible without the
* 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
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class PrinceOfPersiaCheats extends Cheats {
@ -56,7 +55,6 @@ public class PrinceOfPersiaCheats extends Cheats {
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;
@ -166,7 +164,7 @@ public class PrinceOfPersiaCheats extends Cheats {
protected String getDeviceName() {
return ("Prince of Persia");
}
@Override
public void tick() {
// Do nothing
@ -175,94 +173,19 @@ public class PrinceOfPersiaCheats extends Cheats {
@Override
public void registerListeners() {
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);
}
});
addCheat(RAMEvent.TYPE.READ_DATA, CharYVel, this::velocityHackBehavior);
}
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);
}
});
forceValue(KidStrength, true, 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);
}
});
forceValue(EnemyAlert, true, 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);
}
});
forceValue(hasSword, true, 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);
}
});
forceValue(MinLeft, true, 0x069);
}
if (mouseHack) {
EmulatorUILogic.addMouseListener(listener);
@ -275,15 +198,18 @@ public class PrinceOfPersiaCheats extends Cheats {
public void unregisterListeners() {
super.unregisterListeners();
EmulatorUILogic.removeMouseListener(listener);
mouseRegistered = false;
}
public static int BlueType = 0x0b700;
public void registerMouse() {
if (mouseRegistered) {
EmulatorUILogic.addMouseListener(listener);
mouseRegistered = false;
private void velocityHackBehavior(RAMEvent e) {
if (!SoftSwitches.AUXZP.getState()) {
return;
}
int newVel = e.getNewValue();
if (newVel > 5) {
newVel = 1;
}
e.setNewValue(newVel & 0x0ff);
}
public void mouseClicked(MouseButton button) {
@ -291,7 +217,7 @@ public class PrinceOfPersiaCheats extends Cheats {
// Offset y by three pixels to account for tiles above
Double y = mouseY - 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);
@ -310,7 +236,6 @@ public class PrinceOfPersiaCheats extends Cheats {
// 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.getMemory();
PagedMemory auxMem = mem.getAuxMemory();
@ -428,4 +353,4 @@ public class PrinceOfPersiaCheats extends Cheats {
}
}
}
}
}

View File

@ -46,6 +46,7 @@ public abstract class RAM implements Reconfigurable {
/**
* Creates a new instance of RAM
* @param computer
*/
public RAM(Computer computer) {
this.computer = computer;
@ -151,9 +152,6 @@ public abstract class RAM implements Reconfigurable {
int lsb = 0x00ff & read(address, eventType, triggerEvent, requireSynchronization);
int msb = (0x00ff & read(address + 1, eventType, triggerEvent, requireSynchronization)) << 8;
int value = msb + lsb;
// if (generateEvent) {
// callListener(RAMEvent.TYPE.READ, address, value, value);
// }
return value;
}
@ -204,17 +202,47 @@ public abstract class RAM implements Reconfigurable {
addListenerRange(l);
});
}
public void addListener(final RAMListener l) {
public RAMListener observe(RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(address);
}
@Override
protected void doEvent(RAMEvent e) {
handler.handleEvent(e);
}
});
}
public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) {
return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(addressStart);
setScopeEnd(addressEnd);
}
@Override
protected void doEvent(RAMEvent e) {
handler.handleEvent(e);
}
});
}
public RAMListener addListener(final RAMListener l) {
boolean restart = computer.pause();
if (listeners.contains(l)) {
return;
return l;
}
listeners.add(l);
addListenerRange(l);
if (restart) {
computer.resume();
}
return l;
}
public void removeListener(final RAMListener l) {

View File

@ -31,6 +31,10 @@ package jace.core;
*/
public class RAMEvent {
static public interface RAMEventHandler {
public void handleEvent(RAMEvent e);
}
public enum TYPE {
READ(true),

View File

@ -29,8 +29,8 @@ import jace.core.RAMEvent.TYPE;
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public abstract class RAMListener {
public abstract class RAMListener implements RAMEvent.RAMEventHandler {
private RAMEvent.TYPE type;
private RAMEvent.SCOPE scope;
private RAMEvent.VALUE value;
@ -156,7 +156,8 @@ public abstract class RAMListener {
// If we've made it this far then the event is valid.
return true;
}
@Override
public void handleEvent(RAMEvent e) {
if (isRelevant(e)) {
doEvent(e);