Merge branch 'master' of github.com:badvision/lawless-legends

This commit is contained in:
Martin Haye 2018-02-21 09:54:37 -08:00
commit 234e013047
13 changed files with 205 additions and 73 deletions

View File

@ -125,6 +125,16 @@ public class MapEditor extends Editor<Map, MapEditor.DrawMode> implements EventH
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
public void moveMapByY(int amt) {
getCurrentMap().shift(0, amt);
redraw();
}
public void moveMapByX(int amt) {
getCurrentMap().shift(amt, 0);
redraw();
}
@Override
public void buildEditorUI(Pane tileEditorAnchorPane) {
anchorPane = tileEditorAnchorPane;

View File

@ -98,6 +98,9 @@ public class DataUtilities {
private static void logScripts(Scripts scripts, PrintWriter logger) {
if (scripts != null && scripts.getScript() != null || !scripts.getScript().isEmpty()) {
scripts.getScript().forEach((script) -> {
if (script.getBlock() == null) {
return;
}
Queue<Block> evaluateStack = new ArrayDeque<>();
evaluateStack.add(script.getBlock());
int blockCount = 0;

View File

@ -38,30 +38,30 @@ import org.badvision.outlaweditor.ui.UIAction;
* @author brobert
*/
public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable {
public static final long serialVersionUID = 6486309334559843742L;
Map backingMap;
boolean backingMapStale;
int width;
int height;
public TileMap(Map m) {
backingMapStale = false;
width = 0;
height = 0;
loadFromMap(m);
}
public static final double SATURATION = 0.70;
public static final double VALUE = 1.0;
public static double HUE = 180;
private final java.util.Map<Integer, List<Script>> locationScripts = new HashMap<>();
private final java.util.Map<Script, Color> scriptColors = new HashMap<>();
public Optional<Color> getScriptColor(Script s) {
return Optional.ofNullable(scriptColors.get(s));
}
public List<Script> getLocationScripts(int x, int y) {
List<Script> list = locationScripts.get(getMortonNumber(x, y));
if (list != null) {
@ -70,15 +70,18 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
return Collections.EMPTY_LIST;
}
}
public void putLocationScript(int x, int y, Script s) {
if (x < 0 || y < 0) {
return;
}
LocationTrigger trigger = new Script.LocationTrigger();
trigger.setX(x);
trigger.setY(y);
s.getLocationTrigger().add(trigger);
registerLocationScript(x, y, s);
}
public void removeLocationScripts(int x, int y) {
int loc = getMortonNumber(x, y);
List<Script> scripts = locationScripts.get(loc);
@ -92,7 +95,7 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
locationScripts.remove(loc);
ApplicationState.getInstance().getController().redrawScripts();
}
private void registerLocationScript(int x, int y, Script s) {
if (!scriptColors.containsKey(s)) {
scriptColors.put(s, Color.hsb(HUE, SATURATION, 0.75 + Math.cos(HUE / Math.PI / 2.0) / 8.0));
@ -107,7 +110,7 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
list.add(s);
ApplicationState.getInstance().getController().redrawScripts();
}
private int getMortonNumber(int x, int y) {
int morton = 0;
for (int i = 0; i < 16; i++) {
@ -117,7 +120,7 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
}
return morton;
}
public Tile get(int x, int y) {
if (size() <= y || get(y) == null) {
return null;
@ -127,8 +130,11 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
}
return get(y).get(x);
}
public void put(int x, int y, Tile t) {
if (x < 0 || y < 0) {
return;
}
width = Math.max(x + 1, width);
height = Math.max(y + 1, height);
for (int i = size(); i <= y; i++) {
@ -144,11 +150,11 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
row.set(x, t);
backingMapStale = true;
}
public Map getBackingMap() {
return backingMap;
}
public void updateBackingMap() {
ObjectFactory f = new ObjectFactory();
backingMap.getChunk().clear();
@ -170,9 +176,10 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
backingMap.getChunk().add(c);
backingMapStale = false;
}
private void loadFromMap(Map m) {
clear();
locationScripts.clear();
width = 0;
height = 0;
Set<Tile> unknownTiles = new HashSet<>();
@ -220,20 +227,40 @@ public class TileMap extends ArrayList<ArrayList<Tile>> implements Serializable
backingMapStale = false;
}
public static String NULL_TILE_ID = "_";
public static boolean isNullTile(String tileId) {
return tileId.equalsIgnoreCase(NULL_TILE_ID);
}
public void clearScriptTriggersFromMap(Script script) {
script.getLocationTrigger().clear();
locationScripts.values().stream().filter((scripts) -> !(scripts == null)).forEach((scripts) -> {
scripts.remove(script);
});
}
public void removeScriptFromMap(Script script) {
clearScriptTriggersFromMap(script);
backingMap.getScripts().getScript().remove(script);
}
public void shift(int xAmt, int yAmt) {
Scripts scripts = backingMap.getScripts();
if (scripts != null) {
List<Script> allScripts = new ArrayList<>(scripts.getScript());
allScripts.forEach(
s -> s.getLocationTrigger().forEach(
l -> {
l.setX(l.getX() + xAmt);
l.setY(l.getY() + yAmt);
}
)
);
}
backingMap.getChunk().forEach(c -> {
c.setX(c.getX() + xAmt);
c.setY(c.getY() + yAmt);
});
loadFromMap(backingMap);
}
}

View File

@ -149,6 +149,9 @@ public abstract class MapEditorTabController {
// Handler for Button[Button[id=null, styleClass=button moveButton]] onAction
@FXML
abstract public void scrollMapUp(ActionEvent event);
@FXML
abstract public void showShiftUI(ActionEvent event);
public void initalize() {
assert mapEditorAnchorPane != null : "fx:id=\"mapEditorAnchorPane\" was not injected: check your FXML file 'mapEditorTab.fxml'.";

View File

@ -13,8 +13,6 @@ import java.util.HashMap;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.control.cell.ComboBoxListCell;
@ -22,6 +20,7 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javax.xml.bind.JAXBException;
@ -330,7 +329,30 @@ public class MapEditorTabControllerImpl extends MapEditorTabController {
}
redrawMapScripts();
}
@Override
public void showShiftUI(ActionEvent evt) {
Dialog dialog = new Dialog();
dialog.setTitle("Shift map");
dialog.setOnCloseRequest(v->dialog.close());
dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
GridPane pane = new GridPane();
Button moveUp = new Button("");
moveUp.setOnMouseClicked(v->getCurrentEditor().moveMapByY(-1));
pane.add(moveUp, 1, 0);
Button moveDown = new Button("");
moveDown.setOnMouseClicked(v->getCurrentEditor().moveMapByY(1));
pane.add(moveDown, 1, 2);
Button moveLeft = new Button("");
moveLeft.setOnMouseClicked(v->getCurrentEditor().moveMapByX(-1));
pane.add(moveLeft, 0, 1);
Button moveRight = new Button("");
moveRight.setOnMouseClicked(v->getCurrentEditor().moveMapByX(1));
pane.add(moveRight, 2, 1);
dialog.getDialogPane().setContent(pane);
dialog.show();
}
@Override
public void rebuildMapSelectors() {
Map m = mapSelect.getSelectionModel().getSelectedItem();

View File

@ -16,7 +16,7 @@
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane id="mapsTab" minHeight="0.0" minWidth="0.0" prefHeight="480.0" prefWidth="677.0" stylesheets="@styles/applicationui.css" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.badvision.outlaweditor.ui.impl.MapEditorTabControllerImpl">
<AnchorPane id="mapsTab" minHeight="0.0" minWidth="0.0" prefHeight="480.0" prefWidth="677.0" stylesheets="@styles/applicationui.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.badvision.outlaweditor.ui.impl.MapEditorTabControllerImpl">
<children>
<VBox prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
@ -44,6 +44,7 @@
</items>
</Menu>
<MenuItem mnemonicParsing="false" onAction="#mapTogglePanZoom" text="Toggle pan/zoom controls" />
<MenuItem mnemonicParsing="false" onAction="#showShiftUI" text="Shift Map" />
</items>
</MenuButton>
<Label fx:id="cursorInfo" text="CursorInfo" />

File diff suppressed because one or more lines are too long

View File

@ -234,7 +234,11 @@ public class JaceUIController {
}
});
speedSlider.valueProperty().addListener((val, oldValue, newValue) -> setSpeed(newValue.doubleValue()));
Platform.runLater(() -> speedSlider.setValue(Emulator.logic.speedSetting));
Platform.runLater(() -> {
speedSlider.setValue(Emulator.logic.speedSetting);
// Kind of redundant but make sure speed is properly set as if the user did it
setSpeed(Emulator.logic.speedSetting);
});
}
private void connectButtons(Node n) {
@ -253,7 +257,7 @@ public class JaceUIController {
Emulator.logic.speedSetting = (int) speed;
double speedRatio = convertSpeedToRatio(speed);
if (speedRatio > 100.0) {
Emulator.computer.getMotherboard().maxspeed = true;
Emulator.computer.getMotherboard().setMaxSpeed(true);
Motherboard.cpuPerClock = 3;
} else {
if (speedRatio > 25) {
@ -261,8 +265,8 @@ public class JaceUIController {
} else {
Motherboard.cpuPerClock = 1;
}
Emulator.computer.getMotherboard().maxspeed = false;
Emulator.computer.getMotherboard().speedRatio = (int) (speedRatio * 100);
Emulator.computer.getMotherboard().setMaxSpeed(false);
Emulator.computer.getMotherboard().setSpeedInPercentage((int) (speedRatio * 100));
}
Emulator.computer.getMotherboard().reconfigure();
}

View File

@ -305,7 +305,7 @@ public class MetaCheat extends Cheats {
}
int fadeCounter = 0;
int FADE_TIMER_VALUE = (int) (Emulator.computer.getMotherboard().cyclesPerSecond / 60);
int FADE_TIMER_VALUE = (int) (Emulator.computer.getMotherboard().getSpeedInHz() / 60);
@Override
public void tick() {

View File

@ -65,6 +65,9 @@ public class Motherboard extends TimedDevice {
if (oldMotherboard != null) {
miscDevices.addAll(oldMotherboard.miscDevices);
speaker = oldMotherboard.speaker;
accelorationRequestors.addAll(oldMotherboard.accelorationRequestors);
setSpeedInHz(oldMotherboard.getSpeedInHz());
setMaxSpeed(oldMotherboard.isMaxSpeed());
}
}
@ -147,7 +150,7 @@ public class Motherboard extends TimedDevice {
resume();
}
}
static HashSet<Object> accelorationRequestors = new HashSet<>();
HashSet<Object> accelorationRequestors = new HashSet<>();
public void requestSpeed(Object requester) {
accelorationRequestors.add(requester);

View File

@ -21,24 +21,25 @@ package jace.core;
import jace.config.ConfigurableField;
/**
* A timed device is a device which executes so many ticks in a given time
* interval. This is the core of the emulator timing mechanics.
* A timed device is a device which executes so many ticks in a given time interval. This is the core of the emulator
* timing mechanics.
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public abstract class TimedDevice extends Device {
/**
* Creates a new instance of TimedDevice
*
* @param computer
*/
public TimedDevice(Computer computer) {
super(computer);
setSpeed(cyclesPerSecond);
setSpeedInHz(cyclesPerSecond);
}
@ConfigurableField(name = "Speed", description = "(Percentage)")
public int speedRatio = 100;
public long cyclesPerSecond = defaultCyclesPerSecond();
private long cyclesPerSecond = defaultCyclesPerSecond();
@ConfigurableField(name = "Max speed")
public boolean maxspeed = false;
@ -114,13 +115,44 @@ public abstract class TimedDevice extends Device {
long cyclesPerInterval; // How many cycles to wait until a pause interval
long nextSync; // When was the last pause?
public final void setSpeed(long cyclesPerSecond) {
public final int getSpeedRatio() {
return speedRatio;
}
public final void setMaxSpeed(boolean enabled) {
maxspeed = enabled;
if (!enabled) {
disableTempMaxSpeed();
}
}
public final boolean isMaxSpeed() {
return maxspeed;
}
public final long getSpeedInHz() {
return cyclesPerInterval * 100L;
}
public final void setSpeedInHz(long cyclesPerSecond) {
// System.out.println("Raw set speed for " + getName() + " to " + cyclesPerSecond + "hz");
speedRatio = (int) Math.round(cyclesPerSecond * 100.0 / defaultCyclesPerSecond());
cyclesPerInterval = cyclesPerSecond / 100L;
nanosPerInterval = (long) (cyclesPerInterval * NANOS_PER_SECOND / cyclesPerSecond);
// System.out.println("Will pause " + nanosPerInterval + " nanos every " + cyclesPerInterval + " cycles");
cycleTimer = 0;
resetSyncTimer();
}
public final void setSpeedInPercentage(int ratio) {
// System.out.println("Setting " + getName() + " speed ratio to " + speedRatio);
cyclesPerSecond = defaultCyclesPerSecond() * ratio / 100;
if (cyclesPerSecond == 0) {
cyclesPerSecond = defaultCyclesPerSecond();
}
setSpeedInHz(cyclesPerSecond);
}
long skip = 0;
long wait = 0;
@ -171,11 +203,6 @@ public abstract class TimedDevice extends Device {
@Override
public void reconfigure() {
cyclesPerSecond = defaultCyclesPerSecond() * speedRatio / 100;
if (cyclesPerSecond == 0) {
cyclesPerSecond = defaultCyclesPerSecond();
}
setSpeed(cyclesPerSecond);
}
public abstract long defaultCyclesPerSecond();

View File

@ -1,45 +1,48 @@
package jace.lawless;
import jace.apple2e.MOS65C02;
import jace.apple2e.SoftSwitches;
import jace.cheat.Cheats;
import jace.core.Computer;
import jace.core.RAMEvent;
import jace.core.Utility;
import jace.lawless.LawlessVideo.RenderEngine;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Hacks that affect lawless legends gameplay
*/
public class LawlessHacks extends Cheats {
Computer computer;
// Location of font routines
int FONT_ROUTINES = 0x0EC00;
int FONT_ROUTINES_END = 0x0f800;
int FONT_SPEEDUP_CYCLES = 10000;
int FONT_ROUTINES_LEN = 0x0f00;
int ENGINE_ADDR = 0x06000;
int ENGINE_FIRST_OPCODE = ENGINE_ADDR + (13 * 3);
int DETECT_ENGINE_WRITE = 0x060FF;
Cheats speedupRequester = this;
AtomicInteger speedupCounter = new AtomicInteger();
public LawlessHacks(Computer computer) {
super(computer);
this.computer = computer;
}
@Override
public void registerListeners() {
for (int entry = 0; entry < 13; entry++) {
int targetAddress = FONT_ROUTINES + (entry * 3);
addCheat(RAMEvent.TYPE.EXECUTE, (e) -> {
if (e.getAddress() == targetAddress) {
computer.motherboard.requestSpeed(speedupRequester);
speedupCounter.set(FONT_SPEEDUP_CYCLES);
}
}, targetAddress);
}
// Observe graphics changes
addCheat(RAMEvent.TYPE.WRITE, (e) -> {
if (e.getAddress() >= 0x02000 && e.getAddress() <= 0x05FFF) {
((LawlessVideo) computer.getVideo()).setBWFlag(e.getAddress(),
SoftSwitches.RAMWRT.getState() ||
computer.getCpu().getProgramCounter() < FONT_ROUTINES ||
computer.getCpu().getProgramCounter() > FONT_ROUTINES_END);
}
}, 0x02000, 0x05FFF);
// Watch for font routine usage for speedup
addCheat(RAMEvent.TYPE.EXECUTE, (e) -> {
if ((e.getAddress() & 0x0ff00) == FONT_ROUTINES) {
computer.motherboard.requestSpeed(this);
}
}, FONT_ROUTINES, FONT_ROUTINES | 0x0ff);
// Try to detect engines changing
addCheat(RAMEvent.TYPE.WRITE, false, (e) -> {
if (e.getAddress() == DETECT_ENGINE_WRITE) {
detectActiveEngine();
@ -54,18 +57,10 @@ public class LawlessHacks extends Cheats {
@Override
public void tick() {
if (speedupCounter.get() > 0 && speedupCounter.decrementAndGet() <= 0) {
int pc = computer.getCpu().getProgramCounter();
if (pc >= FONT_ROUTINES && pc <= FONT_ROUTINES + FONT_ROUTINES_LEN) {
speedupCounter.addAndGet(500);
computer.motherboard.requestSpeed(speedupRequester);
} else {
computer.motherboard.cancelSpeedRequest(speedupRequester);
}
}
}
private void detectActiveEngine() {
LawlessVideo video = (LawlessVideo) computer.getVideo();
// for (int i = 0x06000; i < 0x06080;) {
// System.out.printf("%04x: ", i);
// for (int j = 0; j < 16; j++, i++) {
@ -79,19 +74,19 @@ public class LawlessHacks extends Cheats {
if (firstPageByte == MOS65C02.OPCODE.JMP_AB.getCode()
&& firstDataByte == MOS65C02.OPCODE.LDX_ZP.getCode()) {
// 2D Engine: First instruction is LDX MAP_PARTITION
LawlessVideo.setEngine(RenderEngine._2D);
video.setEngine(RenderEngine._2D);
} else if (firstPageByte == MOS65C02.OPCODE.JMP_AB.getCode()
&& firstDataByte == 0
&& secondDataByte == 0) {
// 3D Engine: First byte is a zero for MapHeader
LawlessVideo.setEngine(RenderEngine._3D);
video.setEngine(RenderEngine._3D);
} else if (firstPageByte == MOS65C02.OPCODE.JMP_AB.getCode()
&& firstDataByte == 0
&& secondDataByte == 0x067) {
// 3D Engine: First byte is a zero for MapHeader
LawlessVideo.setEngine(RenderEngine.PORTRAIT);
video.setEngine(RenderEngine.PORTRAIT);
} else {
LawlessVideo.setEngine(RenderEngine.UNKNOWN);
video.setEngine(RenderEngine.UNKNOWN);
}
}

View File

@ -1,5 +1,7 @@
package jace.lawless;
import jace.Emulator;
import jace.LawlessLegends;
import jace.apple2e.RAM128k;
import jace.apple2e.VideoNTSC;
import jace.core.Computer;
@ -15,7 +17,9 @@ public class LawlessVideo extends VideoNTSC {
private static RenderEngine activeEngine = RenderEngine.UNKNOWN;
private boolean invActive = false;
private boolean titleScreen = false;
private boolean[][] activeMask = new boolean[192][80];
public static enum RenderEngine {
_2D(new int[]{
9, 8, 34, 17,
@ -61,9 +65,30 @@ public class LawlessVideo extends VideoNTSC {
super(computer);
}
public static void setEngine(RenderEngine e) {
public void setEngine(RenderEngine e) {
activeEngine = e;
// System.out.println("Detected engine: " + e.name());
for (int y=0; y < 192; y++) {
System.arraycopy(e.colorMask[y], 0, activeMask[y], 0, 80);
}
Emulator.computer.onNextVBL(() -> Emulator.computer.getVideo().forceRefresh());
System.out.println("Detected engine: " + e.name());
}
public void setBWFlag(int addr, boolean b) {
addr &= 0x01FFF;
int row = VideoNTSC.identifyHiresRow(addr);
if (row < 0 || row > 192) {
return;
}
int col = addr - VideoNTSC.calculateHiresOffset(row);
if (row > 20 && row < 136 && col < 20) {
boolean prev = activeMask[row][col*2];
activeMask[row][col*2] = b;
activeMask[row][col*2+1] = b;
if (prev ^ b) {
redraw();
}
}
}
static public int[] divBy56 = new int[560];
@ -83,10 +108,10 @@ public class LawlessVideo extends VideoNTSC {
int row6 = getSummary(6);
int row7 = getSummary(7);
// Rows 6,7 = White
invActive = row5 == 0
&& row6 == 1270
invActive = row5 == 0
&& row6 == 1270
&& row7 == 1270;
titleScreen = row4 == 828 && row5 == 513 && row6 == 382;
titleScreen = row4 == 828 && row5 == 513 && row6 == 382;
}
public int getSummary(int row) {
@ -103,7 +128,7 @@ public class LawlessVideo extends VideoNTSC {
public void hblankStart(WritableImage screen, int y, boolean isDirty) {
int rowStart = getCurrentWriter().getYOffset(y);
if (rowStart >= 0x02000) {
boolean[] color = activeEngine.colorMask[y];
boolean[] color = activeMask[y];
if (titleScreen) {
color = RenderEngine.UNKNOWN.colorMask[y];
} else if (invActive) {