mirror of
https://github.com/simon987/Much-Assembly-Required.git
synced 2025-12-17 08:39:06 +00:00
Simplify project structure (wip), run tsc during compilation
This commit is contained in:
94
src/main/java/net/simon987/mar/biomass/BiomassBlob.java
Normal file
94
src/main/java/net/simon987/mar/biomass/BiomassBlob.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package net.simon987.mar.biomass;
|
||||
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.InventoryHolder;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class BiomassBlob extends GameObject implements InventoryHolder {
|
||||
|
||||
private static final char MAP_INFO = 0x0101;
|
||||
|
||||
/**
|
||||
* Yield of the blob, in biomass units
|
||||
*/
|
||||
private int biomassCount;
|
||||
|
||||
public BiomassBlob() {
|
||||
}
|
||||
|
||||
public BiomassBlob(Document document) {
|
||||
super(document);
|
||||
|
||||
biomassCount = document.getInteger("biomassCount");
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
|
||||
JSONObject json = super.jsonSerialise();
|
||||
|
||||
json.put("b", biomassCount);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("biomassCount", biomassCount);
|
||||
|
||||
return dbObject;
|
||||
|
||||
}
|
||||
|
||||
public int getBiomassCount() {
|
||||
return biomassCount;
|
||||
}
|
||||
|
||||
public void setBiomassCount(int biomassCount) {
|
||||
this.biomassCount = biomassCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an object attempts to place an item in this BiomassBlob
|
||||
*
|
||||
* @return Always returns false
|
||||
*/
|
||||
@Override
|
||||
public boolean placeItem(Item item) {
|
||||
//Why would you want to place an item in a blob?
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTakeItem(int itemId) {
|
||||
return itemId == ItemBiomass.ID && biomassCount >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an object attempts to take an item from this BiomassBlob.
|
||||
* If the object requests biomass, it will be subtracted from biomassCount, and
|
||||
* if it reaches 0, the plant is deleted
|
||||
*/
|
||||
@Override
|
||||
public void takeItem(int itemId) {
|
||||
|
||||
if (itemId == ItemBiomass.ID) {
|
||||
if (biomassCount > 1) {
|
||||
biomassCount--;
|
||||
} else {
|
||||
//Delete plant
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/main/java/net/simon987/mar/biomass/ItemBiomass.java
Normal file
36
src/main/java/net/simon987/mar/biomass/ItemBiomass.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package net.simon987.mar.biomass;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
public class ItemBiomass extends Item {
|
||||
|
||||
public static final int ID = 0x0001;
|
||||
|
||||
private static final int energy = GameServer.INSTANCE.getConfig().getInt("biomassEnergyValue");
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
public ItemBiomass() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public ItemBiomass(Document document) {
|
||||
super(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(ControllableUnit unit) {
|
||||
unit.storeEnergy(energy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char poll() {
|
||||
return ID;
|
||||
}
|
||||
}
|
||||
78
src/main/java/net/simon987/mar/biomass/WorldUtils.java
Normal file
78
src/main/java/net/simon987/mar/biomass/WorldUtils.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package net.simon987.mar.biomass;
|
||||
|
||||
import net.simon987.mar.server.game.world.TileMap;
|
||||
import net.simon987.mar.server.game.world.TilePlain;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
public class WorldUtils {
|
||||
|
||||
/**
|
||||
* Generate a list of biomass blobs for a world
|
||||
*/
|
||||
public static ArrayList<BiomassBlob> generateBlobs(World world, int minCount, int maxCount, int yield) {
|
||||
|
||||
Random random = new Random();
|
||||
int blobCount = random.nextInt(maxCount - minCount) + minCount;
|
||||
ArrayList<BiomassBlob> biomassBlobs = new ArrayList<>(blobCount);
|
||||
|
||||
//Count number of plain tiles. If there is less plain tiles than desired amount of blobs,
|
||||
//set the desired amount of blobs to the plain tile count
|
||||
TileMap m = world.getTileMap();
|
||||
int plainCount = 0;
|
||||
for (int y = 0; y < world.getWorldSize(); y++) {
|
||||
for (int x = 0; x < world.getWorldSize(); x++) {
|
||||
|
||||
if (m.getTileIdAt(x, y) == TilePlain.ID) {
|
||||
plainCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blobCount > plainCount) {
|
||||
blobCount = plainCount;
|
||||
}
|
||||
|
||||
outerLoop:
|
||||
for (int i = 0; i < blobCount; i++) {
|
||||
|
||||
Point p = m.getRandomTile(TilePlain.ID);
|
||||
if (p != null) {
|
||||
|
||||
//Don't block worlds
|
||||
int counter = 0;
|
||||
while (p.x == 0 || p.y == 0 || p.x == world.getWorldSize() - 1 || p.y == world.getWorldSize() - 1 ||
|
||||
world.getGameObjectsAt(p.x, p.y).size() != 0) {
|
||||
p = m.getRandomTile(TilePlain.ID);
|
||||
counter++;
|
||||
|
||||
if (counter > 25) {
|
||||
continue outerLoop;
|
||||
}
|
||||
}
|
||||
|
||||
for (BiomassBlob biomassBlob : biomassBlobs) {
|
||||
if (biomassBlob.getX() == p.x && biomassBlob.getY() == p.y) {
|
||||
//There is already a blob here
|
||||
continue outerLoop;
|
||||
}
|
||||
}
|
||||
|
||||
BiomassBlob biomassBlob = new BiomassBlob();
|
||||
biomassBlob.setObjectId(new ObjectId());
|
||||
biomassBlob.setBiomassCount(yield);
|
||||
biomassBlob.setX(p.x);
|
||||
biomassBlob.setY(p.y);
|
||||
biomassBlob.setWorld(world);
|
||||
|
||||
biomassBlobs.add(biomassBlob);
|
||||
}
|
||||
}
|
||||
|
||||
return biomassBlobs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package net.simon987.mar.biomass.event;
|
||||
|
||||
import net.simon987.mar.biomass.BiomassBlob;
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.event.ObjectDeathEvent;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
/**
|
||||
* Handles ObjectDeathEvent events
|
||||
*/
|
||||
public class ObjectDeathListener implements GameEventListener {
|
||||
|
||||
private final int biomassDropCount;
|
||||
|
||||
public ObjectDeathListener(IServerConfiguration config) {
|
||||
biomassDropCount = config.getInt("harvester_biomass_drop_count");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return ObjectDeathEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
if (event.getSource().getClass().getCanonicalName().equals("net.simon987.mar.npcplugin.HarvesterNPC")) {
|
||||
//An HarvesterNPC ObjectDeathEvent is received
|
||||
GameObject dyingHarvesterNPC = (GameObject) event.getSource();
|
||||
|
||||
//Don't spawn biomass on World border
|
||||
if (dyingHarvesterNPC.getX() != 0 && dyingHarvesterNPC.getX() != dyingHarvesterNPC.getWorld().getWorldSize() - 1 &&
|
||||
dyingHarvesterNPC.getY() != 0 && dyingHarvesterNPC.getY() != dyingHarvesterNPC.getWorld().getWorldSize() - 1) {
|
||||
//Create a new biomass
|
||||
BiomassBlob newBiomassBlob = createBiomassBlobAt(
|
||||
dyingHarvesterNPC.getX(), dyingHarvesterNPC.getY(), dyingHarvesterNPC.getWorld());
|
||||
//Add it to the world game objects
|
||||
dyingHarvesterNPC.getWorld().addObject(newBiomassBlob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a biomass at the given x, y coordinates and in the world
|
||||
* @param x x coord of biomass location
|
||||
* @param y y coord of biomass location
|
||||
* @param world world in which the biomass will be created in
|
||||
* @return the new BiomassBlob created
|
||||
*/
|
||||
private BiomassBlob createBiomassBlobAt(int x, int y, World world) {
|
||||
|
||||
BiomassBlob biomassBlob = new BiomassBlob();
|
||||
biomassBlob.setObjectId(new ObjectId());
|
||||
biomassBlob.setBiomassCount(biomassDropCount);
|
||||
biomassBlob.setX(x);
|
||||
biomassBlob.setY(y);
|
||||
biomassBlob.setWorld(world);
|
||||
|
||||
return biomassBlob;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.simon987.mar.biomass.event;
|
||||
|
||||
import net.simon987.mar.biomass.BiomassBlob;
|
||||
import net.simon987.mar.biomass.WorldUtils;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.event.WorldGenerationEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class WorldCreationListener implements GameEventListener {
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return WorldGenerationEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
int minCount = GameServer.INSTANCE.getConfig().getInt("minBiomassCount");
|
||||
int maxCount = GameServer.INSTANCE.getConfig().getInt("maxBiomassCount");
|
||||
int yield = GameServer.INSTANCE.getConfig().getInt("biomass_yield");
|
||||
|
||||
ArrayList<BiomassBlob> biomassBlobs = WorldUtils.generateBlobs(((WorldGenerationEvent) event).getWorld(),
|
||||
minCount, maxCount, yield);
|
||||
|
||||
for (BiomassBlob blob : biomassBlobs) {
|
||||
((WorldGenerationEvent) event).getWorld().addObject(blob);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package net.simon987.mar.biomass.event;
|
||||
|
||||
import net.simon987.mar.biomass.BiomassBlob;
|
||||
import net.simon987.mar.biomass.WorldUtils;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.event.WorldUpdateEvent;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
public class WorldUpdateListener implements GameEventListener {
|
||||
|
||||
private final HashMap<World, Long> worldWaitMap = new HashMap<>(200);
|
||||
|
||||
private static int minBlobCount;
|
||||
private static int maxBlobCount;
|
||||
private static int blobYield;
|
||||
private static int waitTime;
|
||||
private static int blobThreshold;
|
||||
|
||||
public WorldUpdateListener(IServerConfiguration config) {
|
||||
|
||||
minBlobCount = config.getInt("minBiomassRespawnCount");
|
||||
maxBlobCount = config.getInt("maxBiomassRespawnCount");
|
||||
waitTime = config.getInt("biomassRespawnTime");
|
||||
blobThreshold = config.getInt("biomassRespawnThreshold");
|
||||
blobYield = config.getInt("biomass_yield");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return WorldUpdateEvent.class;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
World world = ((WorldUpdateEvent) event).getWorld();
|
||||
|
||||
if (world.getDimension().startsWith("w")) {
|
||||
//If there is less than the respawn threshold,
|
||||
if (world.findObjects(BiomassBlob.class).size() < blobThreshold) {
|
||||
|
||||
//Set a timer for respawn_time ticks
|
||||
if (!worldWaitMap.containsKey(world) || worldWaitMap.get(world) == 0L) {
|
||||
worldWaitMap.put(world, GameServer.INSTANCE.getGameUniverse().getTime() + waitTime);
|
||||
} else {
|
||||
|
||||
long waitUntil = worldWaitMap.get(world);
|
||||
|
||||
if (GameServer.INSTANCE.getGameUniverse().getTime() >= waitUntil) {
|
||||
|
||||
//If the timer was set less than respawn_time ticks ago, respawn the blobs
|
||||
ArrayList<BiomassBlob> newBlobs = WorldUtils.generateBlobs(world, minBlobCount,
|
||||
maxBlobCount, blobYield);
|
||||
for (BiomassBlob blob : newBlobs) {
|
||||
world.addObject(blob);
|
||||
world.incUpdatable();
|
||||
}
|
||||
|
||||
//Set the 'waitUntil' time to 0 to indicate that we are not waiting
|
||||
worldWaitMap.replace(world, 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/main/java/net/simon987/mar/construction/BluePrint.java
Normal file
111
src/main/java/net/simon987/mar/construction/BluePrint.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.InventoryHolder;
|
||||
import net.simon987.mar.server.io.JSONSerializable;
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class BluePrint implements InventoryHolder, JSONSerializable, MongoSerializable {
|
||||
|
||||
/**
|
||||
* Map of items id and required amount
|
||||
* <br>The amount is decremented as the items are added
|
||||
*/
|
||||
protected Map<Integer, Integer> requiredItems;
|
||||
|
||||
/**
|
||||
* This object will be instantiated when completed
|
||||
*/
|
||||
protected Class<? extends GameObject> targetObject;
|
||||
|
||||
/**
|
||||
* Set to true when all the requirements are met
|
||||
*/
|
||||
private boolean completed;
|
||||
|
||||
public BluePrint() {
|
||||
requiredItems = new HashMap<>();
|
||||
}
|
||||
|
||||
public BluePrint(Document document) {
|
||||
requiredItems = (Map<Integer, Integer>) document.get("required_items");
|
||||
completed = document.getBoolean("completed");
|
||||
try {
|
||||
targetObject = Class.forName(document.getString("target")).asSubclass(GameObject.class);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCompleted() {
|
||||
|
||||
for (Integer remaining : requiredItems.values()) {
|
||||
if (remaining > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
completed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeItem(Item item) {
|
||||
|
||||
if (requiredItems.containsKey(item.getId()) && requiredItems.get(item.getId()) > 0) {
|
||||
requiredItems.put(item.getId(), requiredItems.get(item.getId()) - 1);
|
||||
checkCompleted();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void takeItem(int itemId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTakeItem(int itemId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return completed;
|
||||
}
|
||||
|
||||
public Class<? extends GameObject> getTargetObject() {
|
||||
return targetObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
return jsonSerialise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("target", targetObject);
|
||||
json.put("required_items", requiredItems);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = new Document();
|
||||
|
||||
document.put("completed", completed);
|
||||
document.put("target", targetObject);
|
||||
document.put("required_items", requiredItems);
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BluePrintRegistry {
|
||||
|
||||
public static final BluePrintRegistry INSTANCE = new BluePrintRegistry();
|
||||
|
||||
private final Map<String, Class<? extends BluePrint>> blueprints;
|
||||
private final Map<String, String> digitizedBlueprints;
|
||||
|
||||
private BluePrintRegistry() {
|
||||
blueprints = new HashMap<>();
|
||||
digitizedBlueprints = new HashMap<>();
|
||||
}
|
||||
|
||||
public void registerBluePrint(Class<? extends BluePrint> clazz) {
|
||||
blueprints.put(clazz.getCanonicalName(), clazz);
|
||||
String bpData = new String(BluePrintUtil.bluePrintData(clazz));
|
||||
digitizedBlueprints.put(bpData, clazz.getCanonicalName());
|
||||
}
|
||||
|
||||
public BluePrint deserializeBlueprint(Document document) {
|
||||
|
||||
String type = document.getString("type");
|
||||
|
||||
if (blueprints.containsKey(type)) {
|
||||
|
||||
try {
|
||||
return blueprints.get(type).getConstructor(Document.class).newInstance(document);
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
LogManager.LOGGER.severe("(Construction Plugin) Error while trying to deserialize object of type " + type + ": " + e.getTargetException().getMessage());
|
||||
LogManager.LOGGER.severe(document.toJson());
|
||||
e.getTargetException().printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
LogManager.LOGGER.severe("(Construction Plugin) Trying to deserialize unknown BluePrint type: " + type);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public BluePrint deserializeBluePrint(char[] chars) {
|
||||
|
||||
String bpData = new String(chars);
|
||||
|
||||
if (digitizedBlueprints.containsKey(bpData)) {
|
||||
return deserializeBlueprint(new Document("type", digitizedBlueprints.get(bpData)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
class BluePrintUtil {
|
||||
|
||||
private static byte[] secretKey;
|
||||
private static final String SHA512 = "SHA-512";
|
||||
|
||||
//We need 1024 chars = 2048 bytes = 32 values
|
||||
private static final char[] ARBITRARY_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456".toCharArray();
|
||||
|
||||
static void setSecretKey(String secretKey) {
|
||||
BluePrintUtil.secretKey = secretKey.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a message using SHA512 with the server secret key as the sal
|
||||
*
|
||||
* @return 128-bit char array
|
||||
*/
|
||||
private static char[] hashMessage(String message) {
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(SHA512);
|
||||
md.update(secretKey);
|
||||
md.update(message.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
byte[] digest = md.digest();
|
||||
char[] chars = new char[digest.length / 2];
|
||||
ByteBuffer.wrap(digest).order(ByteOrder.BIG_ENDIAN).asCharBuffer().get(chars);
|
||||
|
||||
return chars;
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a char array representation of a blueprint. It is obtained by hashing the blueprint
|
||||
* properties with the server secret key. Some arbitrary values are added to make a 1024-char
|
||||
* array. The same blueprint and secret key always gives the same result.
|
||||
*/
|
||||
static char[] bluePrintData(Class<? extends BluePrint> blueprint) {
|
||||
|
||||
char[] result = new char[ARBITRARY_STRING.length * 32];
|
||||
|
||||
for (int i = ARBITRARY_STRING.length - 1; i > 0; --i) {
|
||||
char[] hashedBlueprint = hashMessage(ARBITRARY_STRING[i] + blueprint.getName());
|
||||
if (hashedBlueprint != null) {
|
||||
System.arraycopy(hashedBlueprint, 0, result,
|
||||
i * hashedBlueprint.length, hashedBlueprint.length);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
|
||||
public class ConstructionPlugin {
|
||||
|
||||
public void init(GameServer gameServer) {
|
||||
// TODO
|
||||
BluePrintUtil.setSecretKey(gameServer.getSecretKey());
|
||||
BluePrintRegistry.INSTANCE.registerBluePrint(ObstacleBlueprint.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.InventoryHolder;
|
||||
import net.simon987.mar.server.game.objects.Structure;
|
||||
import net.simon987.mar.server.game.objects.Updatable;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class ConstructionSite extends Structure implements Updatable, InventoryHolder {
|
||||
|
||||
public static final int MAP_INFO = 0xFFFF; //TODO: determine
|
||||
public static final int LIFETIME = GameServer.INSTANCE.getConfig().getInt("construction_site_ttl");
|
||||
|
||||
private int age;
|
||||
private final BluePrint bluePrint;
|
||||
|
||||
public ConstructionSite(BluePrint bluePrint) {
|
||||
super(1, 1);
|
||||
|
||||
this.bluePrint = bluePrint;
|
||||
this.age = 0;
|
||||
}
|
||||
|
||||
public ConstructionSite(Document document) {
|
||||
super(document, 1, 1);
|
||||
|
||||
age = document.getInteger("age");
|
||||
bluePrint = BluePrintRegistry.INSTANCE.deserializeBlueprint((Document) document.get("blueprint"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
|
||||
age += 1;
|
||||
|
||||
if (age > LIFETIME) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeItem(Item item) {
|
||||
return bluePrint.placeItem(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void takeItem(int itemId) {
|
||||
//NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTakeItem(int itemId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = super.jsonSerialise();
|
||||
|
||||
json.put("blueprint", bluePrint.jsonSerialise());
|
||||
json.put("age", age);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
document.put("blueprint", bluePrint.mongoSerialise());
|
||||
document.put("age", age);
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import net.simon987.mar.server.assembly.Memory;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class ItemBluePrint extends Item {
|
||||
|
||||
public static final int ID = 0x0005;
|
||||
private Class<? extends BluePrint> bluePrint;
|
||||
|
||||
public ItemBluePrint() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public ItemBluePrint(Document document) {
|
||||
super(document);
|
||||
try {
|
||||
bluePrint = Class.forName(document.getString("blueprint")).asSubclass(BluePrint.class);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void digitize(Memory memory, int offset) {
|
||||
char[] data = BluePrintUtil.bluePrintData(bluePrint);
|
||||
memory.write(offset, data, 0, data.length);
|
||||
|
||||
System.out.println("DEBUG: blueprint digitize " + data.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char poll() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
JSONObject json = super.debugJsonSerialise();
|
||||
json.put("blueprint", bluePrint.getCanonicalName());
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
document.put("blueprint", bluePrint.getCanonicalName());
|
||||
return document;
|
||||
}
|
||||
}
|
||||
104
src/main/java/net/simon987/mar/construction/Obstacle.java
Normal file
104
src/main/java/net/simon987/mar/construction/Obstacle.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.Attackable;
|
||||
import net.simon987.mar.server.game.objects.Structure;
|
||||
import net.simon987.mar.server.game.objects.Updatable;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class Obstacle extends Structure implements Attackable, Updatable {
|
||||
|
||||
public static final int MAP_INFO = 0x0601;
|
||||
private static final int HEAL_RATE = GameServer.INSTANCE.getConfig().getInt("obstacle_regen");
|
||||
private static final int MAX_HP = GameServer.INSTANCE.getConfig().getInt("obstacle_hp");
|
||||
|
||||
private int hp;
|
||||
private int color;
|
||||
|
||||
public Obstacle() {
|
||||
super(1, 1);
|
||||
}
|
||||
|
||||
public Obstacle(Document document) {
|
||||
super(document, 1, 1);
|
||||
|
||||
hp = document.getInteger(hp);
|
||||
color = document.getInteger(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
heal(HEAL_RATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHealRate(int hp) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHp() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHp(int hp) {
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHp() {
|
||||
return MAX_HP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxHp(int hp) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void heal(int amount) {
|
||||
hp = Math.min(getMaxHp(), hp + amount);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void damage(int amount) {
|
||||
hp -= amount;
|
||||
|
||||
if (hp < 0) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
document.put("hp", hp);
|
||||
document.put("color", hp);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
return jsonSerialise();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = super.jsonSerialise();
|
||||
|
||||
json.put("hp", hp);
|
||||
json.put("color", hp);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.simon987.mar.construction;
|
||||
|
||||
import net.simon987.mar.server.game.item.ItemIron;
|
||||
import org.bson.Document;
|
||||
|
||||
public class ObstacleBlueprint extends BluePrint {
|
||||
|
||||
public ObstacleBlueprint() {
|
||||
super();
|
||||
|
||||
this.requiredItems.put(ItemIron.ID, 2); //TODO: load from config?
|
||||
this.targetObject = Obstacle.class;
|
||||
}
|
||||
|
||||
public ObstacleBlueprint(Document document) {
|
||||
this.requiredItems.put(ItemIron.ID, 2); //TODO: load from config?
|
||||
this.targetObject = Obstacle.class;
|
||||
}
|
||||
}
|
||||
605
src/main/java/net/simon987/mar/cubot/Cubot.java
Normal file
605
src/main/java/net/simon987/mar/cubot/Cubot.java
Normal file
@@ -0,0 +1,605 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.cubot.event.CubotWalkEvent;
|
||||
import net.simon987.mar.cubot.event.DeathEvent;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Memory;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.assembly.exception.CancelledException;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.item.ItemVoid;
|
||||
import net.simon987.mar.server.game.objects.*;
|
||||
import net.simon987.mar.server.user.User;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public class Cubot extends GameObject implements Updatable, ControllableUnit, MessageReceiver {
|
||||
|
||||
private static final char MAP_INFO = 0x0200;
|
||||
|
||||
/**
|
||||
* Hit points
|
||||
*/
|
||||
private int hp;
|
||||
/**
|
||||
* Maximum hit points
|
||||
*/
|
||||
private int maxHp;
|
||||
|
||||
/**
|
||||
* Shield points
|
||||
*/
|
||||
private int shield;
|
||||
|
||||
/**
|
||||
* Maximum shield points
|
||||
*/
|
||||
private int maxShield;
|
||||
|
||||
/**
|
||||
* Action that was set during the current tick. It is set to IDLE by default
|
||||
*/
|
||||
private Action currentAction = Action.IDLE;
|
||||
|
||||
/**
|
||||
* Action at the end of the last tick
|
||||
*/
|
||||
private Action lastAction = Action.IDLE;
|
||||
|
||||
/**
|
||||
* Status bit field that was set during the current tick. It is set to 0 by default
|
||||
* <br>See CubotStatus and addStatus() method
|
||||
*/
|
||||
private char currentStatus;
|
||||
|
||||
/**
|
||||
* Status bit field at the end of the last tick
|
||||
*/
|
||||
private char lastStatus;
|
||||
|
||||
/**
|
||||
* Buffer of keypress codes. It is not changed between ticks and it is reset when
|
||||
* the player uploads their code
|
||||
*/
|
||||
private ArrayList<Integer> keyboardBuffer = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Buffer of console messages (also called 'internal buffer') that was set during the current tick
|
||||
*/
|
||||
private final ArrayList<char[]> consoleMessagesBuffer = new ArrayList<>(CONSOLE_BUFFER_MAX_SIZE);
|
||||
/**
|
||||
* Buffer of console messages (also called 'internal buffer') at the end of the last tick
|
||||
*/
|
||||
private ArrayList<char[]> lastConsoleMessagesBuffer = new ArrayList<>(CONSOLE_BUFFER_MAX_SIZE);
|
||||
/**
|
||||
* Console mode that was set during the current tick. It is set to NORMAL by default
|
||||
*/
|
||||
private ConsoleMode consoleMode = ConsoleMode.NORMAL;
|
||||
/**
|
||||
* Console mode at the end of the last tick
|
||||
*/
|
||||
private ConsoleMode lastConsoleMode = ConsoleMode.NORMAL;
|
||||
|
||||
/**
|
||||
* User that controls this Cubot
|
||||
*/
|
||||
private User parent;
|
||||
|
||||
/**
|
||||
* Maximum size of the console buffer (also called 'internal buffer')
|
||||
*/
|
||||
public static final int CONSOLE_BUFFER_MAX_SIZE = 40;
|
||||
|
||||
/**
|
||||
* List of attached hardware, 'modules'
|
||||
*/
|
||||
private final Map<Integer, HardwareModule> hardwareAddresses = new HashMap<>();
|
||||
private final Map<Class<? extends HardwareModule>, Integer> hardwareModules = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Cubot's brain box
|
||||
*/
|
||||
private CPU cpu;
|
||||
|
||||
public enum ConsoleMode {
|
||||
/**
|
||||
* Used by the ComPort hardware - clears the console screen (client-side)
|
||||
*/
|
||||
CLEAR,
|
||||
/**
|
||||
* No specific client-side action
|
||||
*/
|
||||
NORMAL
|
||||
}
|
||||
|
||||
public Cubot() {
|
||||
|
||||
}
|
||||
|
||||
public Cubot(Document document) {
|
||||
super(document);
|
||||
|
||||
hp = document.getInteger("hp");
|
||||
shield = document.getInteger("shield");
|
||||
setDirection(Direction.getDirection(document.getInteger("direction")));
|
||||
|
||||
IServerConfiguration config = GameServer.INSTANCE.getConfig();
|
||||
maxHp = config.getInt("cubot_max_hp");
|
||||
maxShield = config.getInt("cubot_max_shield");
|
||||
|
||||
try {
|
||||
cpu = CPU.deserialize((Document) document.get("cpu"), this);
|
||||
|
||||
ArrayList hardwareList = (ArrayList) document.get("hardware");
|
||||
|
||||
for (Object serialisedHw : hardwareList) {
|
||||
HardwareModule hardware = GameServer.INSTANCE.getRegistry().deserializeHardware((Document) serialisedHw, this);
|
||||
hardware.setCpu(cpu);
|
||||
attachHardware(hardware, ((Document) serialisedHw).getInteger("address"));
|
||||
}
|
||||
} catch (CancelledException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
if (currentAction == Action.WALKING) {
|
||||
if (spendEnergy(100)) {
|
||||
if (!incrementLocation()) {
|
||||
//Couldn't walk
|
||||
currentAction = Action.IDLE;
|
||||
}else{
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(new CubotWalkEvent(this));
|
||||
}
|
||||
} else {
|
||||
currentAction = Action.IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* CurrentAction is set during the code execution and this function is called right after
|
||||
* If no action as been set, the action sent to the client is the action in currentAction that
|
||||
* was set last tick (IDLE)
|
||||
*/
|
||||
lastAction = currentAction;
|
||||
currentAction = Action.IDLE;
|
||||
|
||||
//Same principle for hologram
|
||||
|
||||
//And the console
|
||||
lastConsoleMode = consoleMode;
|
||||
consoleMode = ConsoleMode.NORMAL;
|
||||
|
||||
lastConsoleMessagesBuffer = new ArrayList<>(consoleMessagesBuffer);
|
||||
consoleMessagesBuffer.clear();
|
||||
|
||||
//And the status..
|
||||
lastStatus = currentStatus;
|
||||
currentStatus = 0;
|
||||
|
||||
for (HardwareModule module : hardwareAddresses.values()) {
|
||||
module.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = super.jsonSerialise();
|
||||
json.put("direction", getDirection().ordinal());
|
||||
CubotInventory inv = (CubotInventory) getHardware(CubotInventory.class);
|
||||
int heldItem = inv.getInventory().getOrDefault(inv.getPosition(), new ItemVoid()).getId();
|
||||
json.put("heldItem", heldItem);
|
||||
json.put("hp", hp);
|
||||
json.put("shield", shield);
|
||||
json.put("action", lastAction.ordinal());
|
||||
|
||||
if (parent != null) {
|
||||
json.put("parent", parent.getUsername()); //Only used client-side for now
|
||||
}
|
||||
|
||||
for (HardwareModule module : hardwareAddresses.values()) {
|
||||
JSONObject hwJson = module.jsonSerialise();
|
||||
if (hwJson != null) {
|
||||
json.put(module.getClass().getName(), hwJson);
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("direction", getDirection().ordinal());
|
||||
dbObject.put("hp", hp);
|
||||
dbObject.put("shield", shield);
|
||||
dbObject.put("action", lastAction.ordinal());
|
||||
|
||||
if (parent != null) {
|
||||
dbObject.put("parent", parent.getUsername()); //Only used client-side for now
|
||||
}
|
||||
|
||||
List<Document> hardwareList = new ArrayList<>();
|
||||
|
||||
for (Integer address : hardwareAddresses.keySet()) {
|
||||
|
||||
HardwareModule hardware = hardwareAddresses.get(address);
|
||||
|
||||
Document serialisedHw = hardware.mongoSerialise();
|
||||
serialisedHw.put("address", address);
|
||||
hardwareList.add(serialisedHw);
|
||||
}
|
||||
|
||||
dbObject.put("hardware", hardwareList);
|
||||
|
||||
dbObject.put("cpu", cpu.mongoSerialise());
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to 'factory settings', as it were when it was first created
|
||||
*/
|
||||
private void reset() {
|
||||
setDead(false);
|
||||
setHp(maxHp);
|
||||
setShield(0);
|
||||
setEnergy(((CubotBattery) getHardware(CubotBattery.class)).getMaxEnergy());
|
||||
clearKeyboardBuffer();
|
||||
consoleMessagesBuffer.clear();
|
||||
lastConsoleMessagesBuffer.clear();
|
||||
currentStatus = 0;
|
||||
lastStatus = 0;
|
||||
addStatus(CubotStatus.FACTORY_NEW);
|
||||
|
||||
for (HardwareModule module : hardwareAddresses.values()) {
|
||||
module.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeadCallback() {
|
||||
GameEvent event = new DeathEvent(this);
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(event);
|
||||
|
||||
reset();
|
||||
|
||||
//Teleport to spawn point
|
||||
this.getWorld().removeObject(this);
|
||||
this.getWorld().decUpdatable();
|
||||
|
||||
IServerConfiguration config = GameServer.INSTANCE.getConfig();
|
||||
Random random = new Random();
|
||||
|
||||
int spawnX = config.getInt("new_user_worldX") + random.nextInt(5);
|
||||
int spawnY = config.getInt("new_user_worldY") + random.nextInt(5);
|
||||
String dimension = config.getString("new_user_dimension");
|
||||
this.setWorld(GameServer.INSTANCE.getGameUniverse().getWorld(spawnX, spawnY, true, dimension));
|
||||
|
||||
Point point = this.getWorld().getRandomPassableTile();
|
||||
this.setX(point.x);
|
||||
this.setY(point.y);
|
||||
|
||||
this.getWorld().addObject(this);
|
||||
this.getWorld().incUpdatable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyboardBuffer(ArrayList<Integer> kbBuffer) {
|
||||
keyboardBuffer = kbBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Integer> getKeyboardBuffer() {
|
||||
return keyboardBuffer;
|
||||
}
|
||||
|
||||
public void clearKeyboardBuffer() {
|
||||
keyboardBuffer.clear();
|
||||
}
|
||||
|
||||
public void setCurrentAction(Action currentAction) {
|
||||
this.currentAction = currentAction;
|
||||
}
|
||||
|
||||
public void setParent(User parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
return lastAction;
|
||||
}
|
||||
|
||||
public Action getCurrentAction() {
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
public int getEnergy() {
|
||||
CubotBattery battery = (CubotBattery) getHardware(CubotBattery.class);
|
||||
return battery.getEnergy();
|
||||
}
|
||||
|
||||
public void setEnergy(int energy) {
|
||||
CubotBattery battery = (CubotBattery) getHardware(CubotBattery.class);
|
||||
battery.setEnergy(energy);
|
||||
}
|
||||
|
||||
public boolean spendEnergy(int amount) {
|
||||
|
||||
CubotBattery battery = (CubotBattery) getHardware(CubotBattery.class);
|
||||
|
||||
if (battery.getEnergy() - amount < 0) {
|
||||
return false;
|
||||
} else {
|
||||
battery.setEnergy(battery.getEnergy() - amount);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void storeEnergy(int amount) {
|
||||
|
||||
CubotBattery battery = (CubotBattery) getHardware(CubotBattery.class);
|
||||
battery.setEnergy(Math.min(battery.getEnergy() + amount, battery.getMaxEnergy()));
|
||||
}
|
||||
|
||||
public void setMaxEnergy(int maxEnergy) {
|
||||
CubotBattery battery = (CubotBattery) getHardware(CubotBattery.class);
|
||||
battery.setMaxEnergy(maxEnergy);
|
||||
}
|
||||
|
||||
public int getMaxEnergy() {
|
||||
CubotBattery battery = (CubotBattery) getHardware(CubotBattery.class);
|
||||
return battery.getMaxEnergy();
|
||||
}
|
||||
|
||||
public int getShield() {
|
||||
return shield;
|
||||
}
|
||||
|
||||
public void setShield(int shield) {
|
||||
this.shield = shield;
|
||||
}
|
||||
|
||||
public boolean chargeShield(int amount) {
|
||||
amount = Math.min(amount, maxShield - shield);
|
||||
|
||||
int energySpent = amount * CubotShield.COST;
|
||||
if(spendEnergy(energySpent)) {
|
||||
shield += amount;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Damages shield by amount.
|
||||
*
|
||||
* Return damage that broke through the shield.
|
||||
*/
|
||||
public int damageShield(int amount) {
|
||||
int after = shield - amount;
|
||||
if(after < 0) {
|
||||
shield = 0;
|
||||
return -after;
|
||||
}
|
||||
shield = after;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Memory getFloppyData() {
|
||||
|
||||
CubotFloppyDrive drive = (CubotFloppyDrive) getHardware(CubotFloppyDrive.class);
|
||||
|
||||
if (drive.getFloppy() != null) {
|
||||
return drive.getFloppy().getMemory();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAt(int x, int y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAction(Action action) {
|
||||
currentAction = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMessage(char[] message) {
|
||||
|
||||
if (consoleMessagesBuffer.size() < CONSOLE_BUFFER_MAX_SIZE) {
|
||||
consoleMessagesBuffer.add(message);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<char[]> getConsoleMessagesBuffer() {
|
||||
return lastConsoleMessagesBuffer;
|
||||
}
|
||||
|
||||
|
||||
public int getConsoleMode() {
|
||||
return lastConsoleMode.ordinal();
|
||||
}
|
||||
|
||||
public void setConsoleMode(ConsoleMode consoleMode) {
|
||||
this.consoleMode = consoleMode;
|
||||
}
|
||||
|
||||
public void addStatus(CubotStatus status) {
|
||||
|
||||
currentStatus |= status.val;
|
||||
}
|
||||
|
||||
public void removeStatus(CubotStatus status) {
|
||||
|
||||
currentStatus &= (~status.val);
|
||||
}
|
||||
|
||||
public char getStatus() {
|
||||
return lastStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently has no effect
|
||||
*/
|
||||
@Override
|
||||
public void setHealRate(int hp) {
|
||||
//no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHp() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHp(int hp) {
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHp() {
|
||||
return maxHp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxHp(int hp) {
|
||||
this.maxHp = hp;
|
||||
}
|
||||
|
||||
public int getMaxShield() {
|
||||
return maxShield;
|
||||
}
|
||||
|
||||
public void setMaxShield(int maxShield) {
|
||||
this.maxShield = maxShield;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void heal(int amount) {
|
||||
hp += amount;
|
||||
|
||||
//Can't heal above max
|
||||
if (hp > maxHp) {
|
||||
hp = maxHp;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(int amount) {
|
||||
|
||||
//Damage shield first
|
||||
int hullDamage = damageShield(amount);
|
||||
|
||||
hp -= hullDamage;
|
||||
|
||||
if (hp <= 0) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachHardware(HardwareModule hardware, int address) {
|
||||
hardwareAddresses.put(address, hardware);
|
||||
hardwareModules.put(hardware.getClass(), address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachHardware(int address) {
|
||||
hardwareAddresses.remove(address);
|
||||
|
||||
Class<? extends HardwareModule> toRemove = null;
|
||||
for (Class<? extends HardwareModule> clazz : hardwareModules.keySet()) {
|
||||
if (hardwareModules.get(clazz) == address) {
|
||||
toRemove = clazz;
|
||||
}
|
||||
}
|
||||
hardwareModules.remove(toRemove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hardwareInterrupt(int address, Status status) {
|
||||
HardwareModule hardware = hardwareAddresses.get(address);
|
||||
|
||||
if (hardware != null) {
|
||||
hardware.handleInterrupt(status);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hardwareQuery(int address) {
|
||||
HardwareModule hardware = hardwareAddresses.get(address);
|
||||
|
||||
if (hardware != null) {
|
||||
return hardware.getId();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public HardwareModule getHardware(Class<? extends HardwareModule> clazz) {
|
||||
return hardwareAddresses.get(hardwareModules.get(clazz));
|
||||
}
|
||||
|
||||
public HardwareModule getHardware(int address) {
|
||||
return hardwareAddresses.get(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CPU getCpu() {
|
||||
return cpu;
|
||||
}
|
||||
|
||||
public void setCpu(CPU cpu) {
|
||||
this.cpu = cpu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void giveItem(Item item) {
|
||||
//Overwrite item at current position
|
||||
((CubotInventory) getHardware(CubotInventory.class)).putItem(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder(super.toString());
|
||||
|
||||
str.append("\nHardware: \n");
|
||||
for (Integer i : hardwareAddresses.keySet()) {
|
||||
str.append(String.format("%04X", i)).append(":").append(hardwareAddresses.get(i)).append("\n");
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
120
src/main/java/net/simon987/mar/cubot/CubotBattery.java
Normal file
120
src/main/java/net/simon987/mar/cubot/CubotBattery.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class CubotBattery extends HardwareModule {
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 0x000A;
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
public static final char HWID = 0x000A;
|
||||
/**
|
||||
* Solar panel multiplier
|
||||
* <br>TODO: Set this constant in dimension
|
||||
*/
|
||||
private static final float SOLAR_PANEL_MULTIPLIER = 1;
|
||||
|
||||
/**
|
||||
* Energy units in kJ
|
||||
*/
|
||||
private int energy;
|
||||
|
||||
/**
|
||||
* Maximum energy units in kJ
|
||||
*/
|
||||
private int maxEnergy;
|
||||
|
||||
|
||||
private static final int BATTERY_POLL = 1;
|
||||
private static final int BATTERY_GET_MAX_CAPACITY = 2;
|
||||
|
||||
public CubotBattery(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
|
||||
energy = GameServer.INSTANCE.getConfig().getInt("battery_max_energy");
|
||||
maxEnergy = GameServer.INSTANCE.getConfig().getInt("battery_max_energy");
|
||||
}
|
||||
|
||||
public CubotBattery(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
|
||||
energy = document.getInteger("energy");
|
||||
maxEnergy = document.getInteger("max_energy");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == BATTERY_POLL) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(energy);
|
||||
|
||||
} else if (a == BATTERY_GET_MAX_CAPACITY) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(maxEnergy);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("energy", energy);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
JSONObject json = jsonSerialise();
|
||||
|
||||
json.put("max_energy", maxEnergy);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
document.put("energy", energy);
|
||||
document.put("max_energy", maxEnergy);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
public int getEnergy() {
|
||||
return energy;
|
||||
}
|
||||
|
||||
public void setEnergy(int energy) {
|
||||
this.energy = energy;
|
||||
}
|
||||
|
||||
public int getMaxEnergy() {
|
||||
return maxEnergy;
|
||||
}
|
||||
|
||||
public void setMaxEnergy(int maxEnergy) {
|
||||
this.maxEnergy = maxEnergy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
energy = Math.min(maxEnergy,
|
||||
energy + (int) (SOLAR_PANEL_MULTIPLIER * GameServer.INSTANCE.getDayNightCycle().getSunIntensity()));
|
||||
}
|
||||
}
|
||||
130
src/main/java/net/simon987/mar/cubot/CubotComPort.java
Normal file
130
src/main/java/net/simon987/mar/cubot/CubotComPort.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.MessageReceiver;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CubotComPort extends HardwareModule {
|
||||
|
||||
public static final char HWID = 0xD;
|
||||
public static final int DEFAULT_ADDRESS = 0xD;
|
||||
|
||||
private static final int COMPORT_BUFFER_CLEAR = 0;
|
||||
private static final int COMPORT_POLL = 1;
|
||||
private static final int COMPORT_FRONT_PORT_OUT = 2;
|
||||
private static final int COMPORT_SELF_OUT = 3;
|
||||
private static final int COMPORT_CONSOLE_CLEAR = 4;
|
||||
|
||||
public CubotComPort(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public CubotComPort(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
}
|
||||
|
||||
private static final int MESSAGE_LENGTH = 8;
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == COMPORT_BUFFER_CLEAR) {
|
||||
|
||||
unit.getConsoleMessagesBuffer().clear();
|
||||
|
||||
} else if (a == COMPORT_CONSOLE_CLEAR) {
|
||||
|
||||
if (unit instanceof Cubot) {
|
||||
((Cubot) unit).setConsoleMode(Cubot.ConsoleMode.CLEAR);
|
||||
}
|
||||
|
||||
} else if (a == COMPORT_POLL) {
|
||||
|
||||
if (unit.spendEnergy(4)) {
|
||||
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
|
||||
//Read all messages in the console buffer to memory at X
|
||||
|
||||
for (char[] message : unit.getConsoleMessagesBuffer()) {
|
||||
if (x + MESSAGE_LENGTH >= getCpu().getMemory().getWords().length) {
|
||||
//todo set interrupt ?
|
||||
getCpu().getStatus().setErrorFlag(true);
|
||||
} else {
|
||||
System.arraycopy(message, 0, getCpu().getMemory().getWords(), x, MESSAGE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
//Set B = number of messages
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(unit.getConsoleMessagesBuffer().size());
|
||||
|
||||
}
|
||||
|
||||
} else if (a == COMPORT_FRONT_PORT_OUT) {
|
||||
|
||||
if (unit.spendEnergy(5)) {
|
||||
//Get object directly in front of the Cubot
|
||||
Point frontTile = unit.getFrontTile();
|
||||
//Todo will have to add getGameObjectsBlockingAt to enable Factory
|
||||
ArrayList<GameObject> objects = unit.getWorld().getGameObjectsAt(frontTile.x, frontTile.y);
|
||||
|
||||
if (objects.size() > 0 && objects.get(0) instanceof MessageReceiver) {
|
||||
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
|
||||
if (x + MESSAGE_LENGTH >= getCpu().getMemory().getWords().length) {
|
||||
//todo set interrupt ?
|
||||
getCpu().getStatus().setErrorFlag(true);
|
||||
} else {
|
||||
|
||||
//Get MESSAGE_LENGTH-word message pointed by X
|
||||
char[] message = new char[MESSAGE_LENGTH];
|
||||
System.arraycopy(getCpu().getMemory().getWords(), x, message, 0, MESSAGE_LENGTH);
|
||||
|
||||
//Send it to the MessageReceiver object
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(
|
||||
((MessageReceiver) objects.get(0)).sendMessage(message) ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(0); //Failed
|
||||
|
||||
} else if (a == COMPORT_SELF_OUT) {
|
||||
|
||||
if (unit.spendEnergy(1)) {
|
||||
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
|
||||
//Write a single message to console buffer
|
||||
if (x + MESSAGE_LENGTH >= getCpu().getMemory().getWords().length) {
|
||||
//todo set interrupt ?
|
||||
getCpu().getStatus().setErrorFlag(true);
|
||||
} else {
|
||||
|
||||
//Get MESSAGE_LENGTH-word message pointed by X
|
||||
char[] message = new char[MESSAGE_LENGTH];
|
||||
System.arraycopy(getCpu().getMemory().getWords(), x, message, 0, MESSAGE_LENGTH);
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(unit.sendMessage(message) ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(0); //Failed
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
}
|
||||
46
src/main/java/net/simon987/mar/cubot/CubotCore.java
Normal file
46
src/main/java/net/simon987/mar/cubot/CubotCore.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
public class CubotCore extends HardwareModule {
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 0x000E;
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
public static final char HWID = 0x000E;
|
||||
|
||||
private static final int CORE_STATUS_POLL = 1;
|
||||
private static final int CORE_HULL_POLL = 2;
|
||||
|
||||
public CubotCore(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public CubotCore(Document document, ControllableUnit unit) {
|
||||
super(document, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == CORE_STATUS_POLL) {
|
||||
if (unit instanceof Cubot) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(((Cubot) unit).getStatus());
|
||||
}
|
||||
} else if (a == CORE_HULL_POLL) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(unit.getHp());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
}
|
||||
60
src/main/java/net/simon987/mar/cubot/CubotDrill.java
Normal file
60
src/main/java/net/simon987/mar/cubot/CubotDrill.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.Action;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.world.Tile;
|
||||
import org.bson.Document;
|
||||
|
||||
public class CubotDrill extends HardwareModule {
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
static final char HWID = 0x0005;
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 5;
|
||||
|
||||
private static final int DRILL_POLL = 1;
|
||||
private static final int DRILL_GATHER = 2; // simplified gather
|
||||
|
||||
public CubotDrill(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public CubotDrill(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == DRILL_POLL) {
|
||||
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(0);
|
||||
|
||||
} else if (a == DRILL_GATHER) {
|
||||
|
||||
if (unit.spendEnergy(1400)) {
|
||||
if (unit.getCurrentAction() == Action.IDLE) {
|
||||
|
||||
Tile tile = unit.getWorld().getTileMap().getTileAt(unit.getX(), unit.getY());
|
||||
|
||||
Item newItem = tile.drill();
|
||||
if (newItem != null) {
|
||||
unit.setCurrentAction(Action.DIGGING);
|
||||
unit.giveItem(newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
src/main/java/net/simon987/mar/cubot/CubotFloppyDrive.java
Normal file
92
src/main/java/net/simon987/mar/cubot/CubotFloppyDrive.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
public class CubotFloppyDrive extends CubotHardwareModule {
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
static final char HWID = 0x000B;
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 0x000B;
|
||||
|
||||
private static final int FLOPPY_POLL = 1;
|
||||
private static final int FLOPPY_READ_SECTOR = 2;
|
||||
private static final int FLOPPY_WRITE_SECTOR = 3;
|
||||
|
||||
private final FloppyDisk floppyDisk;
|
||||
|
||||
public CubotFloppyDrive(Cubot cubot) {
|
||||
super(cubot);
|
||||
|
||||
floppyDisk = new FloppyDisk();
|
||||
}
|
||||
|
||||
public CubotFloppyDrive(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
|
||||
if (document.containsKey("floppy")) {
|
||||
floppyDisk = new FloppyDisk((Document) document.get("floppy"));
|
||||
} else {
|
||||
floppyDisk = new FloppyDisk();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == FLOPPY_POLL) {
|
||||
|
||||
if (floppyDisk != null) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(0);
|
||||
} else {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(1);
|
||||
}
|
||||
|
||||
} else if (a == FLOPPY_READ_SECTOR) {
|
||||
|
||||
if (floppyDisk == null) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(0);
|
||||
} else {
|
||||
if (cubot.spendEnergy(1)) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(1);
|
||||
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
int y = getCpu().getRegisterSet().getRegister("Y").getValue();
|
||||
|
||||
floppyDisk.readSector(x, cubot.getCpu().getMemory(), y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (a == FLOPPY_WRITE_SECTOR) {
|
||||
if (floppyDisk == null) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(0);
|
||||
} else {
|
||||
if (cubot.spendEnergy(1)) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(1);
|
||||
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
int y = getCpu().getRegisterSet().getRegister("Y").getValue();
|
||||
|
||||
floppyDisk.writeSector(x, cubot.getCpu().getMemory(), y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
|
||||
public FloppyDisk getFloppy() {
|
||||
return floppyDisk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
public abstract class CubotHardwareModule extends HardwareModule {
|
||||
|
||||
protected Cubot cubot;
|
||||
|
||||
public CubotHardwareModule(Document document, ControllableUnit cubot) {
|
||||
this.cubot = (Cubot) cubot;
|
||||
}
|
||||
|
||||
public CubotHardwareModule(Cubot cubot) {
|
||||
this.cubot = cubot;
|
||||
}
|
||||
|
||||
}
|
||||
164
src/main/java/net/simon987/mar/cubot/CubotHologram.java
Normal file
164
src/main/java/net/simon987/mar/cubot/CubotHologram.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class CubotHologram extends HardwareModule {
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
static final char HWID = 0x0009;
|
||||
public static final int DEFAULT_ADDRESS = 9;
|
||||
|
||||
private static final int HOLO_CLEAR = 0;
|
||||
private static final int HOLO_DISPLAY_HEX = 1;
|
||||
private static final int HOLO_DISPLAY_STRING = 2;
|
||||
private static final int HOLO_DISPLAY_DEC = 3;
|
||||
private static final int HOLO_DISPLAY_COLOR = 4;
|
||||
|
||||
private static final int STR_MAX_LEN = 8;
|
||||
|
||||
private int displayValue = 0;
|
||||
private String displayString = "";
|
||||
private HologramMode mode = HologramMode.CLEARED;
|
||||
private HologramMode lastMode = HologramMode.CLEARED;
|
||||
|
||||
/**
|
||||
* Hologram color code. Format is handled by the client
|
||||
*/
|
||||
private int displayColor = 0;
|
||||
|
||||
public CubotHologram(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public CubotHologram(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
|
||||
displayValue = document.getInteger("value");
|
||||
displayColor = document.getInteger("color");
|
||||
displayString = document.getString("string");
|
||||
mode = HologramMode.values()[document.getInteger("mode")];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
char a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == HOLO_CLEAR) {
|
||||
mode = HologramMode.CLEARED;
|
||||
} else if (a == HOLO_DISPLAY_HEX) {
|
||||
displayValue = getCpu().getRegisterSet().getRegister("B").getValue();
|
||||
mode = HologramMode.HEX;
|
||||
} else if (a == HOLO_DISPLAY_STRING) {
|
||||
char x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
//Display zero-terminated string starting at X (max 8 chars)
|
||||
|
||||
StringBuilder holoString = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < STR_MAX_LEN; i++) {
|
||||
|
||||
char nextChar = (char) getCpu().getMemory().get(x + i);
|
||||
|
||||
if (nextChar != 0) {
|
||||
holoString.append((char) getCpu().getMemory().get(x + i));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
displayString = holoString.toString();
|
||||
mode = HologramMode.STRING;
|
||||
} else if (a == HOLO_DISPLAY_DEC) {
|
||||
//Display decimal number
|
||||
displayValue = getCpu().getRegisterSet().getRegister("B").getValue();
|
||||
mode = HologramMode.DEC;
|
||||
|
||||
} else if (a == HOLO_DISPLAY_COLOR) {
|
||||
|
||||
if (unit.spendEnergy(4)) {
|
||||
int b = getCpu().getRegisterSet().getRegister("B").getValue();
|
||||
int c = getCpu().getRegisterSet().getRegister("C").getValue();
|
||||
|
||||
displayColor = (c | (b << 16)); //B:C
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
document.put("color", displayColor);
|
||||
document.put("value", displayValue);
|
||||
document.put("string", displayString);
|
||||
document.put("mode", lastMode.ordinal());
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
JSONObject json = jsonSerialise();
|
||||
|
||||
json.put("lastmode", mode);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("color", displayColor);
|
||||
json.put("value", displayValue);
|
||||
json.put("string", displayString);
|
||||
json.put("mode", lastMode.ordinal());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private enum HologramMode {
|
||||
/**
|
||||
* Display nothing
|
||||
*/
|
||||
CLEARED,
|
||||
/**
|
||||
* Display value as hexadecimal in format 0x0000
|
||||
*/
|
||||
HEX,
|
||||
/**
|
||||
* Display string
|
||||
*/
|
||||
STRING,
|
||||
/**
|
||||
* Display value as decimal
|
||||
*/
|
||||
DEC
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
displayValue = 0;
|
||||
displayColor = 0;
|
||||
displayString = "";
|
||||
mode = HologramMode.CLEARED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
lastMode = mode;
|
||||
mode = HologramMode.CLEARED;
|
||||
}
|
||||
}
|
||||
142
src/main/java/net/simon987/mar/cubot/CubotInventory.java
Normal file
142
src/main/java/net/simon987/mar/cubot/CubotInventory.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CubotInventory extends HardwareModule {
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
static final char HWID = 0x0006;
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 6;
|
||||
|
||||
private static final int INV_CLEAR = 0;
|
||||
private static final int INV_POLL = 1;
|
||||
private static final int INV_SEEK = 2;
|
||||
private static final int INV_SCAN = 3;
|
||||
|
||||
private int inventorySize = 4; //TODO: load from config
|
||||
private final Map<Integer, Item> inventory;
|
||||
private int position = 0;
|
||||
|
||||
|
||||
public CubotInventory(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
|
||||
inventory = new HashMap<>();
|
||||
}
|
||||
|
||||
public CubotInventory(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
|
||||
position = document.getInteger("position");
|
||||
inventorySize = document.getInteger("size");
|
||||
|
||||
inventory = new HashMap<>();
|
||||
for (String i : ((Map<String, Document>) document.get("inventory")).keySet()) {
|
||||
inventory.put(Integer.valueOf(i),
|
||||
GameServer.INSTANCE.getRegistry().deserializeItem(((Map<String, Document>) document.get("inventory")).get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
public void putItem(Item item) {
|
||||
inventory.put(position, item);
|
||||
}
|
||||
|
||||
private void scanItem() {
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
Item item = inventory.get(position);
|
||||
item.digitize(unit.getCpu().getMemory(), x);
|
||||
}
|
||||
|
||||
public Item clearItem() {
|
||||
Item item = inventory.get(position);
|
||||
item.clear(unit);
|
||||
inventory.remove(position);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(int inventoryPosition) {
|
||||
this.position = inventoryPosition;
|
||||
}
|
||||
|
||||
public Map<Integer, Item> getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == INV_POLL) {
|
||||
Item item = inventory.get(position);
|
||||
char result;
|
||||
if (item == null) {
|
||||
result = 0;
|
||||
} else {
|
||||
result = item.poll();
|
||||
}
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(result);
|
||||
|
||||
} else if (a == INV_CLEAR) {
|
||||
if (unit.spendEnergy(100)) {
|
||||
clearItem();
|
||||
}
|
||||
} else if (a == INV_SEEK) {
|
||||
setPosition(getCpu().getRegisterSet().getRegister("X").getValue());
|
||||
} else if (a == INV_SCAN) {
|
||||
if (unit.spendEnergy(200)) {
|
||||
scanItem();
|
||||
clearItem();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
document.put("position", position);
|
||||
document.put("size", inventorySize);
|
||||
|
||||
Document items = new Document();
|
||||
|
||||
for (Integer i : inventory.keySet()) {
|
||||
items.put(i.toString(), inventory.get(i).mongoSerialise());
|
||||
}
|
||||
|
||||
document.put("inventory", items);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String itemList = "";
|
||||
for (Integer i : inventory.keySet()) {
|
||||
itemList += i + ": " + inventory.get(i).getClass().getSimpleName() + ", ";
|
||||
}
|
||||
return String.format("{CubotInventory[%d/%d] @ %d [%s]}", inventory.size(), inventorySize, position, itemList);
|
||||
}
|
||||
}
|
||||
54
src/main/java/net/simon987/mar/cubot/CubotKeyboard.java
Normal file
54
src/main/java/net/simon987/mar/cubot/CubotKeyboard.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
public class CubotKeyboard extends CubotHardwareModule {
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 4;
|
||||
|
||||
private static final int KEYBOARD_CLEAR_BUFFER = 0;
|
||||
private static final int KEYBOARD_FETCH_KEY = 1;
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
public static final char HWID = 0x0004;
|
||||
|
||||
public CubotKeyboard(Cubot cubot) {
|
||||
super(cubot);
|
||||
}
|
||||
|
||||
public CubotKeyboard(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == KEYBOARD_CLEAR_BUFFER) {
|
||||
|
||||
cubot.clearKeyboardBuffer();
|
||||
|
||||
} else if (a == KEYBOARD_FETCH_KEY) {
|
||||
//pop
|
||||
int key = 0;
|
||||
if (cubot.getKeyboardBuffer().size() > 0) {
|
||||
key = cubot.getKeyboardBuffer().get(0);
|
||||
cubot.getKeyboardBuffer().remove(0);
|
||||
}
|
||||
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(key);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
92
src/main/java/net/simon987/mar/cubot/CubotLaser.java
Normal file
92
src/main/java/net/simon987/mar/cubot/CubotLaser.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.*;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CubotLaser extends HardwareModule {
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
static final char HWID = 0x0002;
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 2;
|
||||
|
||||
private static final int LASER_WITHDRAW = 1;
|
||||
private static final int LASER_DEPOSIT = 2;
|
||||
private static final int LASER_ATTACK = 3;
|
||||
|
||||
private static final int LASER_DAMAGE = 25;
|
||||
|
||||
public CubotLaser(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public CubotLaser(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
int b = getCpu().getRegisterSet().getRegister("B").getValue();
|
||||
|
||||
|
||||
if (a == LASER_WITHDRAW) {
|
||||
|
||||
|
||||
Point frontTile = unit.getFrontTile();
|
||||
ArrayList<GameObject> objects = unit.getWorld().getGameObjectsBlockingAt(frontTile.x, frontTile.y);
|
||||
|
||||
if (unit.getCurrentAction() == Action.IDLE && objects.size() > 0) {
|
||||
//FIXME: Problem here if more than 1 object
|
||||
if (objects.get(0) instanceof InventoryHolder) {
|
||||
|
||||
if (((InventoryHolder) objects.get(0)).canTakeItem(b)) {
|
||||
if (unit.spendEnergy(30)) {
|
||||
//Take the item
|
||||
((InventoryHolder) objects.get(0)).takeItem(b);
|
||||
unit.giveItem(GameServer.INSTANCE.getRegistry().makeItem(b));
|
||||
unit.setCurrentAction(Action.WITHDRAWING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (a == LASER_DEPOSIT) {
|
||||
// TODO
|
||||
} else if (a == LASER_ATTACK) {
|
||||
|
||||
if (unit.getCurrentAction() == Action.IDLE) {
|
||||
if (unit.spendEnergy(70)) {
|
||||
|
||||
//Get object directly in front of the Cubot
|
||||
Point frontTile = unit.getFrontTile();
|
||||
ArrayList<GameObject> objects = unit.getWorld().getGameObjectsAt(frontTile.x, frontTile.y);
|
||||
|
||||
//todo: Add option in config to allow PvP
|
||||
if (objects.size() > 0 && objects.get(0) instanceof Attackable && !(objects.get(0) instanceof Cubot)) {
|
||||
((Attackable) objects.get(0)).damage(LASER_DAMAGE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unit.setCurrentAction(Action.ATTACKING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
74
src/main/java/net/simon987/mar/cubot/CubotLeg.java
Normal file
74
src/main/java/net/simon987/mar/cubot/CubotLeg.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.Action;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.Direction;
|
||||
import org.bson.Document;
|
||||
|
||||
public class CubotLeg extends HardwareModule {
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 1;
|
||||
|
||||
private static final int LEGS_SET_DIR = 1;
|
||||
private static final int LEGS_SET_DIR_AND_WALK = 2;
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
static final char HWID = 0x0001;
|
||||
|
||||
public CubotLeg(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public CubotLeg(Document document, ControllableUnit unit) {
|
||||
super(document, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
if (unit.getCurrentAction() == Action.IDLE) {
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
int b = getCpu().getRegisterSet().getRegister("B").getValue();
|
||||
|
||||
if (a == LEGS_SET_DIR) {
|
||||
|
||||
|
||||
Direction dir = Direction.getDirection(b);
|
||||
|
||||
if (dir != null) {
|
||||
if (unit.spendEnergy(20)) {
|
||||
unit.setDirection(Direction.getDirection(b));
|
||||
status.setErrorFlag(false);
|
||||
}
|
||||
} else {
|
||||
status.setErrorFlag(true);
|
||||
}
|
||||
|
||||
|
||||
} else if (a == LEGS_SET_DIR_AND_WALK) {
|
||||
|
||||
if (unit.getMaxEnergy() >= 100) {
|
||||
Direction dir = Direction.getDirection(b);
|
||||
|
||||
if (dir != null) {
|
||||
unit.setDirection(Direction.getDirection(b));
|
||||
status.setErrorFlag(false);
|
||||
} else {
|
||||
status.setErrorFlag(true);
|
||||
}
|
||||
|
||||
unit.setCurrentAction(Action.WALKING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
134
src/main/java/net/simon987/mar/cubot/CubotLidar.java
Normal file
134
src/main/java/net/simon987/mar/cubot/CubotLidar.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Memory;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.pathfinding.Node;
|
||||
import net.simon987.mar.server.game.pathfinding.Pathfinder;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CubotLidar extends HardwareModule {
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
public static final char HWID = 0x0003;
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 3;
|
||||
|
||||
private static final int LIDAR_GET_POS = 1;
|
||||
private static final int LIDAR_GET_PATH = 2;
|
||||
private static final int LIDAR_GET_MAP = 3;
|
||||
private static final int LIDAR_GET_WORLD_POS = 4;
|
||||
private static final int LIDAR_GET_WORLD_SIZE = 5;
|
||||
|
||||
public CubotLidar(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public CubotLidar(Document document, ControllableUnit unit) {
|
||||
super(document, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
switch (a) {
|
||||
case LIDAR_GET_POS:
|
||||
getCpu().getRegisterSet().getRegister("X").setValue(unit.getX());
|
||||
getCpu().getRegisterSet().getRegister("Y").setValue(unit.getY());
|
||||
break;
|
||||
case LIDAR_GET_PATH:
|
||||
if (unit.spendEnergy(50)) {
|
||||
int c = getCpu().getRegisterSet().getRegister("C").getValue();
|
||||
int b = getCpu().getRegisterSet().getRegister("B").getValue();
|
||||
int destX = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
int destY = getCpu().getRegisterSet().getRegister("Y").getValue();
|
||||
|
||||
//Get path
|
||||
ArrayList<Node> nodes = Pathfinder.findPath(unit.getWorld(), unit.getX(), unit.getY(),
|
||||
destX, destY, b);
|
||||
|
||||
//Write to memory
|
||||
Memory mem = getCpu().getMemory();
|
||||
|
||||
int counter = c;
|
||||
|
||||
if (nodes != null) {
|
||||
|
||||
Node lastNode = null;
|
||||
|
||||
for (Node n : nodes) {
|
||||
//Store the path as a sequence of directions
|
||||
if (lastNode == null) {
|
||||
lastNode = n;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n.x < lastNode.x) {
|
||||
//West
|
||||
mem.set(counter++, 3);
|
||||
} else if (n.x > lastNode.x) {
|
||||
//East
|
||||
mem.set(counter++, 1);
|
||||
} else if (n.y < lastNode.y) {
|
||||
//North
|
||||
mem.set(counter++, 0);
|
||||
} else if (n.y > lastNode.y) {
|
||||
//South
|
||||
mem.set(counter++, 2);
|
||||
}
|
||||
|
||||
lastNode = n;
|
||||
}
|
||||
|
||||
//Indicate end of path with 0xAAAA
|
||||
mem.set(counter, 0xAAAA);
|
||||
} else {
|
||||
//Indicate invalid path 0xFFFF
|
||||
mem.set(counter, 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LIDAR_GET_MAP:
|
||||
if (unit.spendEnergy(10)) {
|
||||
char[][] mapInfo = unit.getWorld().getMapInfo();
|
||||
|
||||
//Write map data to the location specified by register X
|
||||
int i = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
for (int y = 0; y < unit.getWorld().getWorldSize(); y++) {
|
||||
for (int x = 0; x < unit.getWorld().getWorldSize(); x++) {
|
||||
getCpu().getMemory().set(i++, mapInfo[x][y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LIDAR_GET_WORLD_SIZE:
|
||||
getCpu().getRegisterSet().getRegister("X").setValue(unit.getWorld().getWorldSize());
|
||||
getCpu().getRegisterSet().getRegister("Y").setValue(unit.getWorld().getWorldSize());
|
||||
break;
|
||||
|
||||
case LIDAR_GET_WORLD_POS:
|
||||
getCpu().getRegisterSet().getRegister("X").setValue(unit.getWorld().getX());
|
||||
getCpu().getRegisterSet().getRegister("Y").setValue(unit.getWorld().getY());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/main/java/net/simon987/mar/cubot/CubotShield.java
Normal file
44
src/main/java/net/simon987/mar/cubot/CubotShield.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
public class CubotShield extends CubotHardwareModule {
|
||||
|
||||
public static final char DEFAULT_ADDRESS = 0x000F;
|
||||
|
||||
static final char HWID = 0x000F;
|
||||
|
||||
private static final int SHIELD_CHARGE = 1;
|
||||
private static final int SHIELD_POLL = 2;
|
||||
|
||||
public static final int COST = GameServer.INSTANCE.getConfig().getInt("shield_energy_cost");
|
||||
|
||||
public CubotShield(Cubot cubot) {
|
||||
super(cubot);
|
||||
}
|
||||
|
||||
public CubotShield(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
// b = amount to charge
|
||||
if(a == SHIELD_CHARGE) {
|
||||
int b = getCpu().getRegisterSet().getRegister("B").getValue();
|
||||
cubot.chargeShield(b);
|
||||
} else if (a == SHIELD_POLL) {
|
||||
int shield = cubot.getShield();
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(shield);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/main/java/net/simon987/mar/cubot/CubotStatus.java
Normal file
19
src/main/java/net/simon987/mar/cubot/CubotStatus.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
/**
|
||||
* Status of a Cubot (Special buff or debuff)
|
||||
*/
|
||||
public enum CubotStatus {
|
||||
|
||||
DEFAULT(0),
|
||||
RADIATED(1),
|
||||
DAMAGED(2),
|
||||
FACTORY_NEW(3);
|
||||
|
||||
public char val;
|
||||
|
||||
CubotStatus(int val) {
|
||||
this.val = (char) val;
|
||||
}
|
||||
|
||||
}
|
||||
103
src/main/java/net/simon987/mar/cubot/FloppyDisk.java
Normal file
103
src/main/java/net/simon987/mar/cubot/FloppyDisk.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package net.simon987.mar.cubot;
|
||||
|
||||
|
||||
import net.simon987.mar.server.assembly.Memory;
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import org.bson.Document;
|
||||
|
||||
/**
|
||||
* Represents a floppy disk that is inside a floppy drive.
|
||||
* Floppies contains 80 tracks with 18 sectors per track.
|
||||
* That's 1440 sectors of 512 words. (total 1,474,560 bytes / 737,280 words / 1.44Mb)
|
||||
*/
|
||||
public class FloppyDisk implements MongoSerializable {
|
||||
|
||||
/**
|
||||
* Contents of the disk
|
||||
*/
|
||||
private final Memory memory;
|
||||
|
||||
/**
|
||||
* Current location of the read/write head.
|
||||
* Used to calculate seek time
|
||||
*/
|
||||
private int rwHeadTrack = 0;
|
||||
|
||||
|
||||
public FloppyDisk() {
|
||||
this.memory = new Memory(512 * 1440);
|
||||
}
|
||||
|
||||
public FloppyDisk(Document document) {
|
||||
this.rwHeadTrack = document.getInteger("rwHeadTrack");
|
||||
this.memory = new Memory((Document) document.get("memory"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 512 words from the specified sector to cpu memory at specified address
|
||||
*
|
||||
* @param sector sector to read (0-1440)
|
||||
* @param cpuMemory Cpu memory to write to
|
||||
* @param ramAddress address of the data to write in CPU memory
|
||||
* @return Whether or not the read operation was in the same track as the last r/w
|
||||
*/
|
||||
public boolean readSector(int sector, Memory cpuMemory, int ramAddress) {
|
||||
|
||||
if (sector <= 1440) {
|
||||
cpuMemory.write(ramAddress, memory.getWords(), sector * 512, 512);
|
||||
|
||||
//Calculate seek time
|
||||
int deltaTrack = (sector / 80) - rwHeadTrack;
|
||||
|
||||
if (deltaTrack != 0) {
|
||||
rwHeadTrack = (sector / 80);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Write 512 words to the specified sector from cpu memory at the specified address
|
||||
*
|
||||
* @param sector sector to write (0-1440)
|
||||
* @param cpuMemory Cpu memory to read from
|
||||
* @param ramAddress address of the data to read in CPU memory
|
||||
* @return Whether or not the read operation was in the same track as the last r/w
|
||||
*/
|
||||
public boolean writeSector(int sector, Memory cpuMemory, int ramAddress) {
|
||||
|
||||
if (sector <= 1440) {
|
||||
memory.write(sector * 512, cpuMemory.getWords(), ramAddress, 512);
|
||||
|
||||
//Calculate seek time
|
||||
int deltaTrack = (sector / 80) - rwHeadTrack;
|
||||
|
||||
if (deltaTrack != 0) {
|
||||
rwHeadTrack = (sector / 80);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = new Document();
|
||||
|
||||
dbObject.put("rwHeadTrack", rwHeadTrack);
|
||||
dbObject.put("memory", memory.mongoSerialise());
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
public Memory getMemory() {
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.cubot.Cubot;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.event.DebugCommandEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
|
||||
/**
|
||||
* Debug command to add shield points to a Cubot
|
||||
*/
|
||||
public class ChargeShieldCommandListener implements GameEventListener {
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return DebugCommandEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
DebugCommandEvent e = (DebugCommandEvent) event;
|
||||
|
||||
if (e.getName().equals("chargeShield")) {
|
||||
|
||||
GameObject cubot = GameServer.INSTANCE.getGameUniverse().getObject(e.getObjectId("objectId"));
|
||||
|
||||
if (cubot != null) {
|
||||
|
||||
if (cubot instanceof Cubot) {
|
||||
|
||||
String hp = ((Cubot) cubot).getHp() + "/" + ((Cubot) cubot).getMaxHp();
|
||||
int oldShield = ((Cubot) cubot).getShield();
|
||||
((Cubot) cubot).chargeShield(e.getInt("amount"));
|
||||
|
||||
e.reply("Success: " + hp + " (" + oldShield + ") -> " + hp + "(" + ((Cubot) cubot).getShield() +
|
||||
")");
|
||||
} else {
|
||||
e.reply("Object is not a Cubot");
|
||||
}
|
||||
|
||||
} else {
|
||||
e.reply("Object not found: " + e.getLong("objectId"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.cubot.*;
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.event.CpuInitialisationEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
|
||||
public class CpuInitialisationListener implements GameEventListener {
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return CpuInitialisationEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
CPU cpu = (CPU) event.getSource();
|
||||
Cubot cubot = (Cubot) ((CpuInitialisationEvent) event).getUnit();
|
||||
cpu.setHardwareHost(cubot);
|
||||
|
||||
CubotLeg legHw = new CubotLeg(cubot);
|
||||
legHw.setCpu(cpu);
|
||||
CubotLaser laserHw = new CubotLaser(cubot);
|
||||
laserHw.setCpu(cpu);
|
||||
CubotLidar radarHw = new CubotLidar(cubot);
|
||||
radarHw.setCpu(cpu);
|
||||
CubotKeyboard keyboard = new CubotKeyboard(cubot);
|
||||
keyboard.setCpu(cpu);
|
||||
CubotDrill drillHw = new CubotDrill(cubot);
|
||||
drillHw.setCpu(cpu);
|
||||
CubotInventory invHw = new CubotInventory(cubot);
|
||||
invHw.setCpu(cpu);
|
||||
CubotHologram emoteHw = new CubotHologram(cubot);
|
||||
emoteHw.setCpu(cpu);
|
||||
CubotBattery batteryHw = new CubotBattery(cubot);
|
||||
batteryHw.setCpu(cpu);
|
||||
CubotFloppyDrive floppyHw = new CubotFloppyDrive(cubot);
|
||||
floppyHw.setCpu(cpu);
|
||||
CubotComPort comPortHw = new CubotComPort(cubot);
|
||||
comPortHw.setCpu(cpu);
|
||||
CubotCore coreHw = new CubotCore(cubot);
|
||||
coreHw.setCpu(cpu);
|
||||
CubotShield shieldHw = new CubotShield(cubot);
|
||||
shieldHw.setCpu(cpu);
|
||||
|
||||
cubot.attachHardware(legHw, CubotLeg.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(laserHw, CubotLaser.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(radarHw, CubotLidar.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(keyboard, CubotKeyboard.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(drillHw, CubotDrill.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(invHw, CubotInventory.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(invHw, CubotInventory.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(emoteHw, CubotHologram.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(batteryHw, CubotBattery.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(floppyHw, CubotFloppyDrive.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(comPortHw, CubotComPort.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(coreHw, CubotCore.DEFAULT_ADDRESS);
|
||||
cubot.attachHardware(shieldHw, CubotShield.DEFAULT_ADDRESS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.cubot.Cubot;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
|
||||
public class CubotWalkEvent extends GameEvent {
|
||||
|
||||
public CubotWalkEvent(Cubot cubot) {
|
||||
setSource(cubot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cubot getSource() {
|
||||
return (Cubot) super.getSource();
|
||||
}
|
||||
|
||||
}
|
||||
16
src/main/java/net/simon987/mar/cubot/event/DeathEvent.java
Normal file
16
src/main/java/net/simon987/mar/cubot/event/DeathEvent.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
|
||||
public class DeathEvent extends GameEvent {
|
||||
|
||||
public DeathEvent(GameObject object) {
|
||||
setSource(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameObject getSource() {
|
||||
return (GameObject) super.getSource();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
|
||||
public class DeathListener implements GameEventListener {
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return DeathEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
DeathEvent DeathEvent = (DeathEvent) event;
|
||||
GameObject object = DeathEvent.getSource();
|
||||
if (object instanceof ControllableUnit) {
|
||||
((ControllableUnit) object).getParent().getStats().incrementStat("death", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.cubot.Cubot;
|
||||
import net.simon987.mar.cubot.CubotInventory;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.event.DebugCommandEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
|
||||
public class PopItemCommandListener implements GameEventListener {
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return DebugCommandEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
DebugCommandEvent e = (DebugCommandEvent) event;
|
||||
|
||||
if (e.getName().equals("clearItem")) {
|
||||
|
||||
GameObject object = GameServer.INSTANCE.getGameUniverse().getObject(e.getObjectId("objectId"));
|
||||
|
||||
if (object != null) {
|
||||
|
||||
if (object instanceof Cubot) {
|
||||
|
||||
CubotInventory inventory = (CubotInventory) ((Cubot) object).getHardware(CubotInventory.class);
|
||||
|
||||
e.reply("Removed item from inventory: " + inventory.clearItem());
|
||||
} else {
|
||||
e.reply("Object is not a Cubot");
|
||||
}
|
||||
|
||||
} else {
|
||||
e.reply("Object not found: " + e.getLong("objectId"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.cubot.Cubot;
|
||||
import net.simon987.mar.cubot.CubotInventory;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.event.DebugCommandEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import org.bson.Document;
|
||||
|
||||
public class PutItemCommandListener implements GameEventListener {
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return DebugCommandEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
DebugCommandEvent e = (DebugCommandEvent) event;
|
||||
|
||||
if (e.getName().equals("putItem")) {
|
||||
|
||||
GameObject object = GameServer.INSTANCE.getGameUniverse().getObject(e.getObjectId("objectId"));
|
||||
|
||||
if (object != null) {
|
||||
|
||||
if (object instanceof Cubot) {
|
||||
|
||||
CubotInventory inventory = (CubotInventory) ((Cubot) object).getHardware(CubotInventory.class);
|
||||
Item item = GameServer.INSTANCE.getRegistry().deserializeItem(Document.parse(e.getString("item")));
|
||||
|
||||
if (item != null) {
|
||||
inventory.putItem(item);
|
||||
e.reply("Set item to " + item.getClass().getSimpleName());
|
||||
|
||||
} else {
|
||||
e.reply("Couldn't deserialize item");
|
||||
}
|
||||
|
||||
} else {
|
||||
e.reply("Object is not a Cubot");
|
||||
}
|
||||
|
||||
} else {
|
||||
e.reply("Object not found: " + e.getLong("objectId"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.cubot.Cubot;
|
||||
import net.simon987.mar.cubot.CubotInventory;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.event.DebugCommandEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
|
||||
public class SetInventoryPosition implements GameEventListener {
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return DebugCommandEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
DebugCommandEvent e = (DebugCommandEvent) event;
|
||||
|
||||
if (e.getName().equals("setInventoryPosition")) {
|
||||
|
||||
GameObject object = GameServer.INSTANCE.getGameUniverse().getObject(e.getObjectId("objectId"));
|
||||
|
||||
if (object != null) {
|
||||
|
||||
if (object instanceof Cubot) {
|
||||
|
||||
int position = e.getInt("position");
|
||||
CubotInventory inventory = (CubotInventory) ((Cubot) object).getHardware(CubotInventory.class);
|
||||
|
||||
inventory.setPosition(position);
|
||||
e.reply("Set inventory position to " + position);
|
||||
} else {
|
||||
e.reply("Object is not a Cubot");
|
||||
}
|
||||
|
||||
} else {
|
||||
e.reply("Object not found: " + e.getLong("objectId"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.cubot.Cubot;
|
||||
import net.simon987.mar.cubot.CubotStatus;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.assembly.Assembler;
|
||||
import net.simon987.mar.server.assembly.AssemblyResult;
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.assembly.exception.CancelledException;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.event.UserCreationEvent;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import net.simon987.mar.server.user.User;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Random;
|
||||
|
||||
public class UserCreationListener implements GameEventListener {
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return UserCreationEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
User user = (User) event.getSource();
|
||||
Cubot cubot = new Cubot();
|
||||
cubot.addStatus(CubotStatus.FACTORY_NEW);
|
||||
cubot.setObjectId(new ObjectId());
|
||||
IServerConfiguration config = GameServer.INSTANCE.getConfig();
|
||||
|
||||
Point point = null;
|
||||
while (point == null || cubot.getWorld() == null) {
|
||||
int spawnX = config.getInt("new_user_worldX") + random.nextInt(5);
|
||||
int spawnY = config.getInt("new_user_worldY") + random.nextInt(5);
|
||||
String dimension = config.getString("new_user_dimension");
|
||||
cubot.setWorld(GameServer.INSTANCE.getGameUniverse().getWorld(spawnX, spawnY, true, dimension));
|
||||
|
||||
point = cubot.getWorld().getRandomPassableTile();
|
||||
}
|
||||
|
||||
cubot.setX(point.x);
|
||||
cubot.setY(point.y);
|
||||
cubot.getWorld().addObject(cubot);
|
||||
cubot.getWorld().incUpdatable();
|
||||
|
||||
cubot.setParent(user);
|
||||
user.setControlledUnit(cubot);
|
||||
|
||||
//Create CPU
|
||||
try {
|
||||
cubot.setCpu(new CPU(GameServer.INSTANCE.getConfig(), cubot));
|
||||
cubot.getCpu().setHardwareHost(cubot);
|
||||
user.setUserCode(config.getString("new_user_code"));
|
||||
|
||||
//Compile user code
|
||||
AssemblyResult ar = new Assembler(cubot.getCpu().getInstructionSet(), cubot.getCpu().getRegisterSet(),
|
||||
GameServer.INSTANCE.getConfig()).parse(user.getUserCode());
|
||||
|
||||
cubot.getCpu().getMemory().clear();
|
||||
|
||||
//Write assembled code to mem
|
||||
char[] assembledCode = ar.getWords();
|
||||
|
||||
cubot.getCpu().getMemory().write((char) ar.origin, assembledCode, 0, assembledCode.length);
|
||||
cubot.getCpu().setCodeSectionOffset(ar.getCodeSectionOffset());
|
||||
} catch (CancelledException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
cubot.setHp(config.getInt("cubot_max_hp"));
|
||||
cubot.setMaxHp(config.getInt("cubot_max_hp"));
|
||||
cubot.setMaxShield(config.getInt("cubot_max_shield"));
|
||||
|
||||
LogManager.LOGGER.fine("Handled User creation event");
|
||||
}
|
||||
}
|
||||
18
src/main/java/net/simon987/mar/cubot/event/WalkListener.java
Normal file
18
src/main/java/net/simon987/mar/cubot/event/WalkListener.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package net.simon987.mar.cubot.event;
|
||||
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
|
||||
public class WalkListener implements GameEventListener {
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return CubotWalkEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
CubotWalkEvent walkEvent = (CubotWalkEvent) event;
|
||||
walkEvent.getSource().getParent().getStats().incrementStat("walkDistance", 1);
|
||||
}
|
||||
}
|
||||
52
src/main/java/net/simon987/mar/mischwplugin/Clock.java
Normal file
52
src/main/java/net/simon987/mar/mischwplugin/Clock.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package net.simon987.mar.mischwplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.assembly.Util;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
/**
|
||||
* Hardware to get game time
|
||||
*/
|
||||
public class Clock extends HardwareModule {
|
||||
|
||||
private static final char HWID = 0x0008;
|
||||
|
||||
public static final char DEFAULT_ADDRESS = 0x0008;
|
||||
|
||||
public Clock() {
|
||||
|
||||
}
|
||||
|
||||
public Clock(Document document, ControllableUnit unit) {
|
||||
super(document, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int time = (int) GameServer.INSTANCE.getGameUniverse().getTime();
|
||||
|
||||
//Will need to be changed to quadword in about 136 years
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(Util.getHigherWord(time));
|
||||
getCpu().getRegisterSet().getRegister("C").setValue(Util.getLowerWord(time));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
|
||||
Document dbObject = new Document();
|
||||
dbObject.put("type", getClass().getCanonicalName());
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package net.simon987.mar.mischwplugin;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Hardware to generate random numbers
|
||||
*/
|
||||
public class RandomNumberGenerator extends HardwareModule {
|
||||
|
||||
private static final char HWID = 0x0007;
|
||||
|
||||
public static final char DEFAULT_ADDRESS = 0x0007;
|
||||
|
||||
private final Random random;
|
||||
|
||||
public RandomNumberGenerator() {
|
||||
random = new Random();
|
||||
}
|
||||
|
||||
public RandomNumberGenerator(Document document, ControllableUnit unit) {
|
||||
super(document, unit);
|
||||
random = new Random();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(random.nextInt(0xFFFF));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
|
||||
Document dbObject = new Document();
|
||||
dbObject.put("type", getClass().getCanonicalName());
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.simon987.mar.mischwplugin.event;
|
||||
|
||||
import net.simon987.mar.mischwplugin.Clock;
|
||||
import net.simon987.mar.mischwplugin.RandomNumberGenerator;
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.event.CpuInitialisationEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.objects.HardwareHost;
|
||||
|
||||
public class CpuInitialisationListener implements GameEventListener {
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return CpuInitialisationEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
CPU cpu = (CPU) event.getSource();
|
||||
HardwareHost cubot = ((CpuInitialisationEvent) event).getUnit();
|
||||
cpu.setHardwareHost(cubot);
|
||||
|
||||
RandomNumberGenerator rngHW = new RandomNumberGenerator();
|
||||
rngHW.setCpu(cpu);
|
||||
Clock clock = new Clock();
|
||||
clock.setCpu(cpu);
|
||||
|
||||
cpu.getHardwareHost().attachHardware(rngHW, RandomNumberGenerator.DEFAULT_ADDRESS);
|
||||
cpu.getHardwareHost().attachHardware(clock, Clock.DEFAULT_ADDRESS);
|
||||
}
|
||||
}
|
||||
162
src/main/java/net/simon987/mar/npcplugin/ElectricBox.java
Normal file
162
src/main/java/net/simon987/mar/npcplugin/ElectricBox.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.Util;
|
||||
import net.simon987.mar.server.game.objects.Attackable;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.Rechargeable;
|
||||
import net.simon987.mar.server.game.objects.Updatable;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Game object that deals damage to nearby objects and gives them energy
|
||||
*/
|
||||
public class ElectricBox extends GameObject implements Updatable, Attackable {
|
||||
|
||||
private static final char MAP_INFO = 0x0301;
|
||||
/**
|
||||
* Hit points
|
||||
*/
|
||||
private int hp;
|
||||
/**
|
||||
* Maximum hit points
|
||||
*/
|
||||
private static final int maxHp = GameServer.INSTANCE.getConfig().getInt("electric_box_hp");
|
||||
/**
|
||||
* Number of hit points dealt to nearby objects each tick
|
||||
*/
|
||||
private static final int damageDealt = GameServer.INSTANCE.getConfig().getInt("electric_box_damage");
|
||||
/**
|
||||
* Number of energy points given to nearby objects each tick
|
||||
*/
|
||||
private static final int energyGiven = GameServer.INSTANCE.getConfig().getInt("electric_box_energy_given");
|
||||
|
||||
/**
|
||||
* List of nearby objects. Is updated every tick
|
||||
*/
|
||||
private final ArrayList<Attackable> nearObjects = new ArrayList<>();
|
||||
|
||||
public ElectricBox() {
|
||||
hp = maxHp;
|
||||
}
|
||||
|
||||
public ElectricBox(Document document) {
|
||||
super(document);
|
||||
hp = document.getInteger("hp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently has no effect
|
||||
*/
|
||||
@Override
|
||||
public void setHealRate(int hp) {
|
||||
//no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHp() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHp(int hp) {
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHp() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently has no effect
|
||||
*/
|
||||
@Override
|
||||
public void setMaxHp(int hp) {
|
||||
//No op
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently has no effect
|
||||
*/
|
||||
@Override
|
||||
public void heal(int amount) {
|
||||
//No op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(int amount) {
|
||||
hp -= amount;
|
||||
|
||||
//YOU ARE DEAD
|
||||
if (hp <= 0) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current list nearby objects
|
||||
* <br>An object is considered 'nearby' if its Manhattan distance is {@literal <= @} 1 and is Attackable
|
||||
*/
|
||||
private void updateNearObjects() {
|
||||
|
||||
nearObjects.clear();
|
||||
|
||||
for (GameObject object : getWorld().getGameObjects()) {
|
||||
if (object != this && object instanceof Attackable && Util.manhattanDist(object.getX(), object.getY(),
|
||||
getX(), getY()) <= 1) {
|
||||
nearObjects.add((Attackable) object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every tick
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
|
||||
updateNearObjects();
|
||||
|
||||
for (Attackable obj : nearObjects) {
|
||||
obj.damage(damageDealt);
|
||||
|
||||
if (obj instanceof Rechargeable) {
|
||||
((Rechargeable) obj).storeEnergy(energyGiven);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = super.jsonSerialise();
|
||||
|
||||
json.put("hp", hp);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("hp", getHp());
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeadCallback() {
|
||||
getWorld().decUpdatable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
38
src/main/java/net/simon987/mar/npcplugin/ExecuteCpuTask.java
Normal file
38
src/main/java/net/simon987/mar/npcplugin/ExecuteCpuTask.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.Action;
|
||||
|
||||
public class ExecuteCpuTask extends NPCTask {
|
||||
|
||||
private static final int MAX_EXEC_TIME = GameServer.INSTANCE.getConfig().getInt("npc_exec_time");
|
||||
|
||||
@Override
|
||||
public boolean checkCompleted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(NonPlayerCharacter npc) {
|
||||
|
||||
HackedNPC hNpc = (HackedNPC) npc;
|
||||
|
||||
//Execute code
|
||||
int timeout = Math.min(hNpc.getEnergy(), MAX_EXEC_TIME);
|
||||
hNpc.getCpu().reset();
|
||||
int cost = hNpc.getCpu().execute(timeout);
|
||||
hNpc.spendEnergy(cost);
|
||||
|
||||
if (hNpc.getCurrentAction() == Action.WALKING) {
|
||||
if (hNpc.spendEnergy(100)) {
|
||||
if (hNpc.incrementLocation()) {
|
||||
//Couldn't walk
|
||||
hNpc.setCurrentAction(Action.IDLE);
|
||||
}
|
||||
} else {
|
||||
hNpc.setCurrentAction(Action.IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
157
src/main/java/net/simon987/mar/npcplugin/Factory.java
Normal file
157
src/main/java/net/simon987/mar/npcplugin/Factory.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.MessageReceiver;
|
||||
import net.simon987.mar.server.game.objects.Structure;
|
||||
import net.simon987.mar.server.game.objects.Updatable;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Game objects that regularly creates NonPlayerCharacters
|
||||
*/
|
||||
public class Factory extends Structure implements Updatable, MessageReceiver {
|
||||
|
||||
private static final int MAP_INFO = 0x0401;
|
||||
|
||||
/**
|
||||
* Maximum number of NonPlayerCharacters assigned to this Factory
|
||||
*/
|
||||
private static final int MAX_NPC_COUNT = GameServer.INSTANCE.getConfig().getInt("factory_max_npc_count");
|
||||
|
||||
/**
|
||||
* Number of ticks to wait after creating a NonPlayerCharacter
|
||||
*/
|
||||
private static final int NPC_CREATION_COOLDOWN = NonPlayerCharacter.LIFETIME / MAX_NPC_COUNT;
|
||||
|
||||
/**
|
||||
* Number of ticks to wait until the Factory can spawn a new NPC
|
||||
*/
|
||||
private int cooldown = 0;
|
||||
|
||||
private boolean locked = true;
|
||||
|
||||
/**
|
||||
* If non-null, the next spawned NPC will be a HackedNPC and the program will be
|
||||
* injected in its memory
|
||||
*/
|
||||
private char[] program;
|
||||
private int programIndex = 0;
|
||||
|
||||
private static final int PROGRAM_SIZE = GameServer.INSTANCE.getConfig().getInt("factory_program_size");
|
||||
|
||||
public Factory() {
|
||||
super(2, 2);
|
||||
}
|
||||
|
||||
public Factory(Document document) {
|
||||
super(document, 2, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every tick
|
||||
* <br>The fist time this is called, NPCs retrieved from the database are linked to the Factory
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
|
||||
Settlement settlement = NpcPlugin.settlementMap.get(getWorld().getId());
|
||||
|
||||
if (settlement == null) {
|
||||
//Only happens when server is killed during save function
|
||||
getWorld().decUpdatable();
|
||||
setDead(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cooldown == 0) {
|
||||
if (settlement.getNpcs().size() < MAX_NPC_COUNT) {
|
||||
Point p = getAdjacentTile();
|
||||
|
||||
if (p != null) {
|
||||
NonPlayerCharacter npc = spawnNPC(p);
|
||||
settlement.addNpc(npc);
|
||||
|
||||
getWorld().addObject(npc);
|
||||
getWorld().incUpdatable();
|
||||
}
|
||||
}
|
||||
|
||||
cooldown += NPC_CREATION_COOLDOWN;
|
||||
|
||||
} else {
|
||||
cooldown--;
|
||||
}
|
||||
}
|
||||
|
||||
private NonPlayerCharacter spawnNPC(Point p) {
|
||||
|
||||
NonPlayerCharacter npc;
|
||||
|
||||
if (programIndex == 0) {
|
||||
npc = spawnRandomNpc(p);
|
||||
} else {
|
||||
npc = spawnHackedNpc(p);
|
||||
}
|
||||
|
||||
return npc;
|
||||
}
|
||||
|
||||
private NonPlayerCharacter spawnRandomNpc(Point p) {
|
||||
NonPlayerCharacter npc;
|
||||
npc = new HarvesterNPC();
|
||||
npc.setWorld(getWorld());
|
||||
npc.setObjectId(new ObjectId());
|
||||
npc.setX(p.x);
|
||||
npc.setY(p.y);
|
||||
return npc;
|
||||
}
|
||||
|
||||
private NonPlayerCharacter spawnHackedNpc(Point p) {
|
||||
NonPlayerCharacter npc;
|
||||
npc = new HackedNPC(program);
|
||||
npc.setWorld(getWorld());
|
||||
npc.setObjectId(new ObjectId());
|
||||
npc.setX(p.x);
|
||||
npc.setY(p.y);
|
||||
|
||||
this.locked = true;
|
||||
this.programIndex = 0;
|
||||
|
||||
return npc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMessage(char[] message) {
|
||||
|
||||
if (locked) {
|
||||
Settlement settlement = NpcPlugin.settlementMap.get(getWorld().getId());
|
||||
|
||||
if (Arrays.equals(settlement.getPassword(), message)) {
|
||||
this.locked = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (programIndex <= PROGRAM_SIZE) {
|
||||
|
||||
if (programIndex == 0) {
|
||||
program = new char[PROGRAM_SIZE];
|
||||
}
|
||||
|
||||
System.arraycopy(message, 0, program, programIndex, message.length);
|
||||
programIndex += message.length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
339
src/main/java/net/simon987/mar/npcplugin/HackedNPC.java
Normal file
339
src/main/java/net/simon987/mar/npcplugin/HackedNPC.java
Normal file
@@ -0,0 +1,339 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.*;
|
||||
import net.simon987.mar.server.event.ObjectDeathEvent;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.item.ItemVoid;
|
||||
import net.simon987.mar.server.game.objects.Action;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.Direction;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import net.simon987.mar.server.user.User;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HackedNPC extends NonPlayerCharacter implements ControllableUnit {
|
||||
|
||||
private static final int MEM_SIZE = GameServer.INSTANCE.getConfig().getInt("hacked_npc_mem_size");
|
||||
private static final boolean DIE_ON_NO_ENERGY = GameServer.INSTANCE.getConfig().getInt("hacked_npc_die_on_no_energy") != 0;
|
||||
|
||||
private final CPU cpu;
|
||||
/**
|
||||
* List of attached hardware, 'modules'
|
||||
*/
|
||||
private final Map<Integer, HardwareModule> hardwareAddresses = new HashMap<>();
|
||||
private final Map<Class<? extends HardwareModule>, Integer> hardwareModules = new HashMap<>();
|
||||
|
||||
private Action currentAction = Action.IDLE;
|
||||
private Action lastAction = Action.IDLE;
|
||||
private final List<char[]> consoleMessagesBuffer = new ArrayList<>(30); //todo load from conf
|
||||
private ArrayList<char[]> lastConsoleMessagesBuffer = new ArrayList<>(30);
|
||||
|
||||
HackedNPC(char[] program) {
|
||||
|
||||
cpu = new CPU();
|
||||
|
||||
cpu.setMemory(new Memory(MEM_SIZE));
|
||||
cpu.setHardwareHost(this);
|
||||
cpu.getMemory().write(cpu.getCodeSectionOffset(), program, 0, program.length);
|
||||
|
||||
for (Object serialisedHw : (List) NpcPlugin.DEFAULT_HACKED_NPC.get("hardware")) {
|
||||
HardwareModule hardware = GameServer.INSTANCE.getRegistry().deserializeHardware((Document) serialisedHw, this);
|
||||
hardware.setCpu(cpu);
|
||||
attachHardware(hardware, ((Document) serialisedHw).getInteger("address"));
|
||||
}
|
||||
|
||||
setTask(new ExecuteCpuTask());
|
||||
}
|
||||
|
||||
public HackedNPC(Document document) {
|
||||
super(document);
|
||||
|
||||
setHp(document.getInteger("hp"));
|
||||
setDirection(Direction.getDirection(document.getInteger("direction")));
|
||||
|
||||
cpu = new CPU();
|
||||
cpu.setHardwareHost(this);
|
||||
cpu.setMemory(new Memory((Document) document.get("memory")));
|
||||
cpu.setRegisterSet(RegisterSet.deserialize((Document) document.get("registerSet")));
|
||||
|
||||
ArrayList hardwareList = (ArrayList) document.get("hardware");
|
||||
|
||||
for (Object serialisedHw : hardwareList) {
|
||||
HardwareModule hardware = GameServer.INSTANCE.getRegistry().deserializeHardware((Document) serialisedHw, this);
|
||||
hardware.setCpu(cpu);
|
||||
attachHardware(hardware, ((Document) serialisedHw).getInteger("address"));
|
||||
}
|
||||
|
||||
setTask(new ExecuteCpuTask());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
super.update();
|
||||
|
||||
lastAction = currentAction;
|
||||
currentAction = Action.IDLE;
|
||||
|
||||
lastConsoleMessagesBuffer = new ArrayList<>(consoleMessagesBuffer);
|
||||
consoleMessagesBuffer.clear();
|
||||
|
||||
for (HardwareModule module : hardwareAddresses.values()) {
|
||||
module.update();
|
||||
}
|
||||
|
||||
//Self-destroy when age limit is reached
|
||||
if (getAge() >= NonPlayerCharacter.LIFETIME) {
|
||||
setDead(true);
|
||||
}
|
||||
|
||||
//Don't bother calling checkCompleted()
|
||||
getTask().tick(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyboardBuffer(ArrayList<Integer> kbBuffer) {
|
||||
LogManager.LOGGER.warning("Something went wrong here: Hacked NPC has no keyboard module" +
|
||||
"@HackedNPC::setKeyBoardBuffer()");
|
||||
Thread.dumpStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(User user) {
|
||||
LogManager.LOGGER.warning("Something went wrong here: Hacked NPC has no parent" +
|
||||
"@HackedNPC::setParent()");
|
||||
Thread.dumpStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getParent() {
|
||||
LogManager.LOGGER.warning("Something went wrong here: Hacked NPC has no parent" +
|
||||
"@HackedNPC::getParent()");
|
||||
Thread.dumpStack();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Integer> getKeyboardBuffer() {
|
||||
LogManager.LOGGER.warning("Something went wrong here: Hacked NPC has no keyboard module" +
|
||||
"@HackedNPC::getKeyBoardBuffer()");
|
||||
Thread.dumpStack();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Memory getFloppyData() {
|
||||
LogManager.LOGGER.warning("Something went wrong here: Hacked NPC has floppy data." +
|
||||
"@HackedNPC::getFloppyData()");
|
||||
Thread.dumpStack();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAction(Action action) {
|
||||
currentAction = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<char[]> getConsoleMessagesBuffer() {
|
||||
return lastConsoleMessagesBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConsoleMode() {
|
||||
LogManager.LOGGER.warning("Something went wrong here: Hacked NPC has no console UI." +
|
||||
"@HackedNPC::getConsoleMode()");
|
||||
Thread.dumpStack();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CPU getCpu() {
|
||||
return cpu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void giveItem(Item item) {
|
||||
//Overwrite item at current position
|
||||
((NpcInventory) getHardware(NpcInventory.class)).putItem(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachHardware(HardwareModule hardware, int address) {
|
||||
hardwareAddresses.put(address, hardware);
|
||||
hardwareModules.put(hardware.getClass(), address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachHardware(int address) {
|
||||
hardwareAddresses.remove(address);
|
||||
|
||||
Class<? extends HardwareModule> toRemove = null;
|
||||
for (Class<? extends HardwareModule> clazz : hardwareModules.keySet()) {
|
||||
if (hardwareModules.get(clazz) == address) {
|
||||
toRemove = clazz;
|
||||
}
|
||||
}
|
||||
hardwareModules.remove(toRemove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hardwareInterrupt(int address, Status status) {
|
||||
HardwareModule hardware = hardwareAddresses.get(address);
|
||||
|
||||
if (hardware != null) {
|
||||
hardware.handleInterrupt(status);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hardwareQuery(int address) {
|
||||
HardwareModule hardware = hardwareAddresses.get(address);
|
||||
|
||||
if (hardware != null) {
|
||||
return hardware.getId();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int getEnergy() {
|
||||
NpcBattery battery = (NpcBattery) getHardware(NpcBattery.class);
|
||||
return battery.getEnergy();
|
||||
}
|
||||
|
||||
public void setEnergy(int energy) {
|
||||
NpcBattery battery = (NpcBattery) getHardware(NpcBattery.class);
|
||||
battery.setEnergy(energy);
|
||||
|
||||
if (energy == 0 && DIE_ON_NO_ENERGY) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean spendEnergy(int amount) {
|
||||
|
||||
NpcBattery battery = (NpcBattery) getHardware(NpcBattery.class);
|
||||
|
||||
if (battery.getEnergy() - amount < 0) {
|
||||
if (DIE_ON_NO_ENERGY) {
|
||||
setDead(true);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
battery.setEnergy(battery.getEnergy() - amount);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("direction", getDirection().ordinal());
|
||||
dbObject.put("hp", getHp());
|
||||
dbObject.put("action", lastAction.ordinal());
|
||||
|
||||
List<Document> hardwareList = new ArrayList<>();
|
||||
|
||||
for (Integer address : hardwareAddresses.keySet()) {
|
||||
|
||||
HardwareModule hardware = hardwareAddresses.get(address);
|
||||
|
||||
Document serialisedHw = hardware.mongoSerialise();
|
||||
serialisedHw.put("address", address);
|
||||
hardwareList.add(serialisedHw);
|
||||
}
|
||||
|
||||
dbObject.put("hardware", hardwareList);
|
||||
|
||||
dbObject.put("memory", cpu.getMemory().mongoSerialise());
|
||||
|
||||
dbObject.put("registerSet", cpu.getRegisterSet().mongoSerialise());
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
public void storeEnergy(int amount) {
|
||||
|
||||
NpcBattery battery = (NpcBattery) getHardware(NpcBattery.class);
|
||||
battery.setEnergy(Math.min(battery.getEnergy() + amount, battery.getMaxEnergy()));
|
||||
}
|
||||
|
||||
private HardwareModule getHardware(Class<? extends HardwareModule> clazz) {
|
||||
return hardwareAddresses.get(hardwareModules.get(clazz));
|
||||
}
|
||||
|
||||
public void setMaxEnergy(int maxEnergy) {
|
||||
NpcBattery battery = (NpcBattery) getHardware(NpcBattery.class);
|
||||
battery.setMaxEnergy(maxEnergy);
|
||||
}
|
||||
|
||||
public int getMaxEnergy() {
|
||||
NpcBattery battery = (NpcBattery) getHardware(NpcBattery.class);
|
||||
return battery.getMaxEnergy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMessage(char[] message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentAction(Action action) {
|
||||
currentAction = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action getCurrentAction() {
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = super.jsonSerialise();
|
||||
|
||||
for (HardwareModule module : hardwareAddresses.values()) {
|
||||
JSONObject hwJson = module.jsonSerialise();
|
||||
if (hwJson != null) {
|
||||
json.put(module.getClass().getName(), hwJson);
|
||||
}
|
||||
}
|
||||
|
||||
json.put("direction", getDirection().ordinal());
|
||||
NpcInventory inv = (NpcInventory) getHardware(NpcInventory.class);
|
||||
Item item = inv.getItem();
|
||||
json.put("heldItem", item == null ? new ItemVoid().getId() : item.getId());
|
||||
json.put("hp", getHp());
|
||||
json.put("action", lastAction.ordinal());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeadCallback() {
|
||||
|
||||
getWorld().decUpdatable();
|
||||
|
||||
if (getSettlement() != null && getSettlement().getNpcs() != null) {
|
||||
getSettlement().getNpcs().remove(this);
|
||||
}
|
||||
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(new ObjectDeathEvent(this));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
return jsonSerialise();
|
||||
}
|
||||
}
|
||||
107
src/main/java/net/simon987/mar/npcplugin/HarvestTask.java
Normal file
107
src/main/java/net/simon987/mar/npcplugin/HarvestTask.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
|
||||
import net.simon987.mar.server.assembly.Util;
|
||||
import net.simon987.mar.server.game.objects.Direction;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.InventoryHolder;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Find Biomass, move towards it, collect it, repeat
|
||||
*/
|
||||
public class HarvestTask extends NPCTask {
|
||||
|
||||
private final Random random;
|
||||
|
||||
/**
|
||||
* Number of ticks to wait before continuing
|
||||
*/
|
||||
private int pause;
|
||||
|
||||
/**
|
||||
* Direction of the next world to visit (randomly chosen)
|
||||
*/
|
||||
private Direction nextWorldDirection = null;
|
||||
|
||||
public HarvestTask() {
|
||||
random = new Random();
|
||||
pause = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This task never finishes
|
||||
*/
|
||||
@Override
|
||||
public boolean checkCompleted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(NonPlayerCharacter npc) {
|
||||
|
||||
if (pause == 0) {
|
||||
//Get biomass
|
||||
ArrayList<GameObject> biomass = npc.getWorld().findGameObjects("net.simon987.mar.biomassplugin.BiomassBlob");
|
||||
|
||||
//Get closest one
|
||||
int minDist = Integer.MAX_VALUE;
|
||||
GameObject minBiomass = null;
|
||||
|
||||
for (GameObject plant : biomass) {
|
||||
|
||||
int dist = Util.manhattanDist(npc.getX(), npc.getY(), plant.getX(), plant.getY());
|
||||
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
minBiomass = plant;
|
||||
}
|
||||
}
|
||||
|
||||
//Move towards it
|
||||
if (minBiomass != null && minDist == 1) {
|
||||
//Reached biomass, change direction to face it
|
||||
Direction newDirection = Direction.getFacing(npc.getX(), npc.getY(),
|
||||
minBiomass.getX(), minBiomass.getY());
|
||||
|
||||
if (newDirection != null) {
|
||||
npc.setDirection(newDirection);
|
||||
|
||||
//Reached biomass, harvest it
|
||||
if (minBiomass instanceof InventoryHolder) {
|
||||
((InventoryHolder) minBiomass).takeItem(1);
|
||||
pause += 6;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
LogManager.LOGGER.severe("FIXME: tick:HarvestTask, Direction is null");
|
||||
}
|
||||
|
||||
nextWorldDirection = null;
|
||||
} else if (minBiomass != null && npc.moveTo(minBiomass.getX(), minBiomass.getY(), 1)) {
|
||||
//Moving towards biomass...
|
||||
nextWorldDirection = null;
|
||||
} else {
|
||||
|
||||
if (nextWorldDirection == null) {
|
||||
|
||||
while (!npc.gotoWorld(nextWorldDirection)) {
|
||||
nextWorldDirection = Direction.getDirection(random.nextInt(4));
|
||||
}
|
||||
|
||||
pause += 6;
|
||||
} else {
|
||||
npc.gotoWorld(nextWorldDirection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
pause--;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/main/java/net/simon987/mar/npcplugin/HarvesterNPC.java
Normal file
95
src/main/java/net/simon987/mar/npcplugin/HarvesterNPC.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.event.ObjectDeathEvent;
|
||||
import net.simon987.mar.server.game.objects.Direction;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
|
||||
public class HarvesterNPC extends NonPlayerCharacter {
|
||||
|
||||
public static final int MAX_HEALTH = GameServer.INSTANCE.getConfig().getInt("harvester_hp_max");
|
||||
public static final int HEAL_RATE = GameServer.INSTANCE.getConfig().getInt("harvester_regen");
|
||||
|
||||
public HarvesterNPC() {
|
||||
setTask(new HarvestTask());
|
||||
|
||||
setHp(MAX_HEALTH);
|
||||
setMaxHp(MAX_HEALTH);
|
||||
setHealRate(HEAL_RATE);
|
||||
}
|
||||
|
||||
public HarvesterNPC(Document document) {
|
||||
super(document);
|
||||
|
||||
setTask(new HarvestTask());
|
||||
|
||||
setDirection(Direction.getDirection(document.getInteger("direction")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
super.update();
|
||||
|
||||
if (getSettlement() != null) {
|
||||
if (getTask().checkCompleted()) {
|
||||
|
||||
setTask(new HarvestTask());
|
||||
|
||||
} else {
|
||||
getTask().tick(this);
|
||||
}
|
||||
|
||||
//Self-destroy when age limit is reached
|
||||
if (getAge() >= NonPlayerCharacter.LIFETIME) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeadCallback() {
|
||||
|
||||
getWorld().decUpdatable();
|
||||
|
||||
if (getSettlement() != null && getSettlement().getNpcs() != null) {
|
||||
getSettlement().getNpcs().remove(this);
|
||||
}
|
||||
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(new ObjectDeathEvent(this));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = super.jsonSerialise();
|
||||
|
||||
json.put("direction", getDirection().ordinal());
|
||||
json.put("hp", getHp());
|
||||
json.put("action", getAction().ordinal());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
JSONObject json = jsonSerialise();
|
||||
|
||||
json.put("taskCompleted", getTask().checkCompleted());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("direction", getDirection().ordinal());
|
||||
dbObject.put("hp", getHp());
|
||||
dbObject.put("action", getAction().ordinal());
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
}
|
||||
10
src/main/java/net/simon987/mar/npcplugin/NPCTask.java
Normal file
10
src/main/java/net/simon987/mar/npcplugin/NPCTask.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
|
||||
public abstract class NPCTask {
|
||||
|
||||
public abstract boolean checkCompleted();
|
||||
|
||||
public abstract void tick(NonPlayerCharacter npc);
|
||||
|
||||
}
|
||||
255
src/main/java/net/simon987/mar/npcplugin/NonPlayerCharacter.java
Normal file
255
src/main/java/net/simon987/mar/npcplugin/NonPlayerCharacter.java
Normal file
@@ -0,0 +1,255 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.Util;
|
||||
import net.simon987.mar.server.game.objects.*;
|
||||
import net.simon987.mar.server.game.pathfinding.Node;
|
||||
import net.simon987.mar.server.game.pathfinding.Pathfinder;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Game object that actively interacts with the game world by doing tasks
|
||||
*/
|
||||
public abstract class NonPlayerCharacter extends GameObject implements Updatable, Attackable {
|
||||
|
||||
private static final char MAP_INFO = 0x0501;
|
||||
|
||||
/**
|
||||
* Maximum distance to travel from its factory, in Worlds
|
||||
*/
|
||||
private static final int MAX_FACTORY_DISTANCE = GameServer.INSTANCE.getConfig().getInt("npc_max_factory_distance");
|
||||
|
||||
/**
|
||||
* Number of ticks to live
|
||||
*/
|
||||
public static final int LIFETIME = GameServer.INSTANCE.getConfig().getInt("npc_lifetime");
|
||||
|
||||
// Set these just in case they aren't overridden in the subclass
|
||||
public static final int HP_MAX_DEFAULT = 100;
|
||||
public static final int HP_REGEN_RATE_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Current task
|
||||
*/
|
||||
private NPCTask task;
|
||||
|
||||
/**
|
||||
* Action at the end of the last tick
|
||||
*/
|
||||
private Action lastAction = Action.IDLE;
|
||||
|
||||
private Settlement settlement;
|
||||
|
||||
/**
|
||||
* Age of the npc, in ticks
|
||||
*/
|
||||
private int age = 0;
|
||||
|
||||
/**
|
||||
* Current health of the npc
|
||||
*/
|
||||
private int hp = HP_MAX_DEFAULT;
|
||||
|
||||
/**
|
||||
* Health regeneration rate of the npc
|
||||
*/
|
||||
private int hpRegenerationRate = HP_REGEN_RATE_DEFAULT;
|
||||
|
||||
/**
|
||||
* Maximum health of the npc
|
||||
*/
|
||||
private int maxHp = HP_MAX_DEFAULT;
|
||||
|
||||
public NonPlayerCharacter() {
|
||||
|
||||
}
|
||||
|
||||
public NonPlayerCharacter(Document document) {
|
||||
super(document);
|
||||
|
||||
hp = document.getInteger("hp");
|
||||
setDirection(Direction.getDirection(document.getInteger("direction")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
|
||||
age++;
|
||||
|
||||
//Destroy NPCs that are not linked with a Settlement
|
||||
if (settlement == null) {
|
||||
setDead(true);
|
||||
}
|
||||
|
||||
//Heal the NPC
|
||||
heal(hpRegenerationRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to move the NPC to the specified coordinates
|
||||
*
|
||||
* @param range distance to the desired coordinates, in tiles
|
||||
* @return true if the path is passable
|
||||
*/
|
||||
boolean moveTo(int x, int y, int range) {
|
||||
|
||||
ArrayList<Node> path = Pathfinder.findPath(getWorld(), getX(), getY(), x, y, range);
|
||||
|
||||
if (path != null && path.size() > 0) {
|
||||
|
||||
Node nextTile = path.get(1);
|
||||
|
||||
Direction newDirection = Direction.getFacing(getX(), getY(), nextTile.x, nextTile.y);
|
||||
|
||||
if (newDirection != null) {
|
||||
setDirection(newDirection);
|
||||
} else {
|
||||
LogManager.LOGGER.severe("FIXME: moveTo:NonPlayerCharacter, Direction is null");
|
||||
}
|
||||
|
||||
if (incrementLocation()) {
|
||||
lastAction = Action.WALKING;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
lastAction = Action.IDLE;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next World in the specified Direction.
|
||||
*
|
||||
* @return true if the World in the specified Direction is within the max. distance from the Factory
|
||||
*/
|
||||
boolean gotoWorld(Direction direction) {
|
||||
|
||||
if (direction == Direction.NORTH) {
|
||||
|
||||
if (Util.manhattanDist(settlement.getWorld().getX(), settlement.getWorld().getY(),
|
||||
getWorld().getX(), getWorld().getY() - 1) <= MAX_FACTORY_DISTANCE) {
|
||||
if (!moveTo(8, 0, 0)) {
|
||||
setDirection(Direction.NORTH);
|
||||
incrementLocation();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (direction == Direction.EAST) {
|
||||
if (Util.manhattanDist(settlement.getWorld().getX(), settlement.getWorld().getY(),
|
||||
getWorld().getX() + 1, getWorld().getY()) <= MAX_FACTORY_DISTANCE) {
|
||||
if (!moveTo(15, 7, 0)) {
|
||||
setDirection(Direction.EAST);
|
||||
incrementLocation();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (direction == Direction.SOUTH) {
|
||||
if (Util.manhattanDist(settlement.getWorld().getX(), settlement.getWorld().getY(),
|
||||
getWorld().getX(), getWorld().getY() + 1) <= MAX_FACTORY_DISTANCE) {
|
||||
if (!moveTo(8, 15, 0)) {
|
||||
setDirection(Direction.SOUTH);
|
||||
incrementLocation();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (direction == Direction.WEST) {
|
||||
if (Util.manhattanDist(settlement.getWorld().getX(), settlement.getWorld().getY(),
|
||||
getWorld().getX() - 1, getWorld().getY()) <= MAX_FACTORY_DISTANCE) {
|
||||
if (!moveTo(0, 7, 0)) {
|
||||
setDirection(Direction.WEST);
|
||||
incrementLocation();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHealRate(int hp) {
|
||||
hpRegenerationRate = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHp() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHp(int hp) {
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHp() {
|
||||
return maxHp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxHp(int hp) {
|
||||
this.maxHp = hp;
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void heal(int amount) {
|
||||
hp += amount;
|
||||
|
||||
//Can't heal above max
|
||||
if (hp > maxHp) {
|
||||
hp = maxHp;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(int amount) {
|
||||
hp -= amount;
|
||||
|
||||
//YOU ARE DEAD
|
||||
if (hp <= 0) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
|
||||
public NPCTask getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
public void setTask(NPCTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
return lastAction;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public Settlement getSettlement() {
|
||||
return settlement;
|
||||
}
|
||||
|
||||
public void setSettlement(Settlement settlement) {
|
||||
this.settlement = settlement;
|
||||
}
|
||||
}
|
||||
109
src/main/java/net/simon987/mar/npcplugin/NpcBattery.java
Normal file
109
src/main/java/net/simon987/mar/npcplugin/NpcBattery.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class NpcBattery extends HardwareModule {
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 0x010A;
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
public static final char HWID = 0x010A;
|
||||
|
||||
/**
|
||||
* Energy units in kJ
|
||||
*/
|
||||
private int energy;
|
||||
|
||||
/**
|
||||
* Maximum energy units in kJ
|
||||
*/
|
||||
private int maxEnergy;
|
||||
|
||||
|
||||
private static final int BATTERY_POLL = 1;
|
||||
private static final int BATTERY_GET_MAX_CAPACITY = 2;
|
||||
|
||||
public NpcBattery(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
|
||||
energy = GameServer.INSTANCE.getConfig().getInt("battery_max_energy");
|
||||
maxEnergy = GameServer.INSTANCE.getConfig().getInt("battery_max_energy");
|
||||
}
|
||||
|
||||
public NpcBattery(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
|
||||
energy = document.getInteger("energy");
|
||||
maxEnergy = document.getInteger("max_energy");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == BATTERY_POLL) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(unit.getEnergy());
|
||||
|
||||
} else if (a == BATTERY_GET_MAX_CAPACITY) {
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(unit.getMaxEnergy());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("energy", energy);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
JSONObject json = jsonSerialise();
|
||||
|
||||
json.put("max_energy", maxEnergy);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
document.put("energy", energy);
|
||||
document.put("max_energy", maxEnergy);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
public int getEnergy() {
|
||||
return energy;
|
||||
}
|
||||
|
||||
public void setEnergy(int energy) {
|
||||
this.energy = energy;
|
||||
}
|
||||
|
||||
public int getMaxEnergy() {
|
||||
return maxEnergy;
|
||||
}
|
||||
|
||||
public void setMaxEnergy(int maxEnergy) {
|
||||
this.maxEnergy = maxEnergy;
|
||||
}
|
||||
}
|
||||
109
src/main/java/net/simon987/mar/npcplugin/NpcInventory.java
Normal file
109
src/main/java/net/simon987/mar/npcplugin/NpcInventory.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.game.item.Item;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
|
||||
public class NpcInventory extends HardwareModule {
|
||||
|
||||
/**
|
||||
* Hardware ID (Should be unique)
|
||||
*/
|
||||
static final char HWID = 0x0106;
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 0x0106;
|
||||
|
||||
private static final int INV_CLEAR = 0;
|
||||
private static final int INV_POLL = 1;
|
||||
private static final int INV_SCAN = 3;
|
||||
|
||||
private Item item;
|
||||
|
||||
public NpcInventory(ControllableUnit unit) {
|
||||
super(null, unit);
|
||||
}
|
||||
|
||||
public NpcInventory(Document document, ControllableUnit cubot) {
|
||||
super(document, cubot);
|
||||
|
||||
Document itemDoc = (Document) document.get("item");
|
||||
if (itemDoc != null) {
|
||||
item = GameServer.INSTANCE.getRegistry().deserializeItem(itemDoc);
|
||||
}
|
||||
}
|
||||
|
||||
public void putItem(Item item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
private void scanItem() {
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
item.digitize(unit.getCpu().getMemory(), x);
|
||||
}
|
||||
|
||||
public Item clearItem() {
|
||||
|
||||
Item oldItem = item;
|
||||
item.clear(unit);
|
||||
item = null;
|
||||
|
||||
return oldItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
public Item getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == INV_POLL) {
|
||||
char result;
|
||||
if (item == null) {
|
||||
result = 0;
|
||||
} else {
|
||||
result = item.poll();
|
||||
}
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(result);
|
||||
|
||||
} else if (a == INV_CLEAR) {
|
||||
if (unit.spendEnergy(100)) {
|
||||
clearItem();
|
||||
}
|
||||
} else if (a == INV_SCAN) {
|
||||
if (unit.spendEnergy(200)) {
|
||||
scanItem();
|
||||
clearItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = super.mongoSerialise();
|
||||
|
||||
if (item != null) {
|
||||
document.put("item", item.mongoSerialise());
|
||||
} else {
|
||||
document.put("item", null);
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{NpcInventory [%s]}", item);
|
||||
}
|
||||
}
|
||||
27
src/main/java/net/simon987/mar/npcplugin/NpcPlugin.java
Normal file
27
src/main/java/net/simon987/mar/npcplugin/NpcPlugin.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class NpcPlugin {
|
||||
|
||||
public static Map<String, Settlement> settlementMap;
|
||||
|
||||
public static Document DEFAULT_HACKED_NPC;
|
||||
|
||||
public void init(GameServer gameServer) {
|
||||
// TODO: save this in GameUniverse.store
|
||||
settlementMap = new ConcurrentHashMap<>();
|
||||
|
||||
// TODO: load from file relpath
|
||||
InputStream is = getClass().getClassLoader().getResourceAsStream("defaultHackedCubotHardware.json");
|
||||
Scanner scanner = new Scanner(is).useDelimiter("\\A");
|
||||
String json = scanner.next();
|
||||
DEFAULT_HACKED_NPC = Document.parse(json);
|
||||
}
|
||||
}
|
||||
113
src/main/java/net/simon987/mar/npcplugin/Obstacle.java
Normal file
113
src/main/java/net/simon987/mar/npcplugin/Obstacle.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.game.objects.Attackable;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
/**
|
||||
* Generic game object that blocks the path.
|
||||
*/
|
||||
public class Obstacle extends GameObject implements Attackable {
|
||||
|
||||
public static final int MAP_INFO = 0x0701;
|
||||
|
||||
/**
|
||||
* Style of the obstacle. Will tell the client which sprite to display
|
||||
*/
|
||||
private int style = 0;
|
||||
|
||||
/**
|
||||
* Current health of the npc
|
||||
*/
|
||||
private int hp;
|
||||
|
||||
/**
|
||||
* Maximum health of the npc
|
||||
*/
|
||||
private int maxHp;
|
||||
|
||||
public Obstacle(int hp) {
|
||||
this.hp = hp;
|
||||
this.maxHp = hp;
|
||||
}
|
||||
|
||||
public Obstacle(Document document) {
|
||||
super(document);
|
||||
style = document.getInteger("style");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHealRate(int hp) {
|
||||
//No op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void heal(int amount) {
|
||||
//No op
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHp() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHp(int hp) {
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHp() {
|
||||
return maxHp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxHp(int hp) {
|
||||
this.maxHp = hp;
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(int amount) {
|
||||
hp -= amount;
|
||||
|
||||
//YOU ARE DEAD
|
||||
if (hp <= 0) {
|
||||
setDead(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
public int getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
public void setStyle(int style) {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("hp", hp);
|
||||
dbObject.put("style", style);
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
JSONObject json = super.jsonSerialise();
|
||||
|
||||
json.put("hp", hp);
|
||||
json.put("style", style);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
89
src/main/java/net/simon987/mar/npcplugin/Portal.java
Normal file
89
src/main/java/net/simon987/mar/npcplugin/Portal.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.Enterable;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.Structure;
|
||||
import net.simon987.mar.server.game.objects.Updatable;
|
||||
import net.simon987.mar.server.game.world.Location;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import org.bson.Document;
|
||||
|
||||
|
||||
public class Portal extends Structure implements Enterable {
|
||||
|
||||
/**
|
||||
* Destination location
|
||||
*/
|
||||
private Location destination;
|
||||
|
||||
public static final int MAP_INFO = 0x0801;
|
||||
|
||||
public Portal() {
|
||||
super(1, 1);
|
||||
}
|
||||
|
||||
public Portal(Document document) {
|
||||
super(document, 1, 1);
|
||||
|
||||
destination = new Location(
|
||||
document.getInteger("dstWorldX"),
|
||||
document.getInteger("dstWorldY"),
|
||||
document.getString("dstDimension"),
|
||||
document.getInteger("dstX"),
|
||||
document.getInteger("dstY"));
|
||||
setX(document.getInteger("x"));
|
||||
setY(document.getInteger("y"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an object attempts to walk directly into a Enterable object
|
||||
*
|
||||
* @param object The game object that attempted to enter
|
||||
* @return true if successful, false to block the object
|
||||
*/
|
||||
@Override
|
||||
public boolean enter(GameObject object) {
|
||||
|
||||
World world = GameServer.INSTANCE.getGameUniverse().getWorld(destination.worldX, destination.worldY, false, destination.dimension);
|
||||
|
||||
if (object instanceof Updatable) {
|
||||
object.getWorld().decUpdatable();
|
||||
world.incUpdatable();
|
||||
}
|
||||
object.getWorld().removeObject(object);
|
||||
object.setWorld(world);
|
||||
world.addObject(object);
|
||||
|
||||
object.setX(destination.x);
|
||||
object.setY(destination.y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("dstWorldX", destination.worldX);
|
||||
dbObject.put("dstWorldY", destination.worldY);
|
||||
dbObject.put("dstX", destination.x);
|
||||
dbObject.put("dstY", destination.y);
|
||||
dbObject.put("dstDimension", destination.dimension);
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
public Location getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void setDestination(Location destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.assembly.HardwareModule;
|
||||
import net.simon987.mar.server.assembly.Status;
|
||||
import net.simon987.mar.server.assembly.Util;
|
||||
import net.simon987.mar.server.game.objects.Action;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RadioReceiverHardware extends HardwareModule {
|
||||
|
||||
public static final char HWID = 0xC; //12
|
||||
|
||||
private static final int LISTEN = 1;
|
||||
|
||||
public static final int DEFAULT_ADDRESS = 0xC;
|
||||
|
||||
private final ControllableUnit cubot;
|
||||
|
||||
public RadioReceiverHardware(ControllableUnit cubot) {
|
||||
this.cubot = cubot;
|
||||
}
|
||||
|
||||
public RadioReceiverHardware(Document document, ControllableUnit unit) {
|
||||
super(document, unit);
|
||||
|
||||
this.cubot = unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInterrupt(Status status) {
|
||||
int x = getCpu().getRegisterSet().getRegister("X").getValue();
|
||||
int a = getCpu().getRegisterSet().getRegister("A").getValue();
|
||||
|
||||
if (a == LISTEN) {
|
||||
|
||||
//Find the nearest Radio Tower and query it
|
||||
cubot.setAction(Action.LISTENING);
|
||||
|
||||
List<char[]> messages = new ArrayList<>(6);
|
||||
|
||||
for (String world : NpcPlugin.settlementMap.keySet()) {
|
||||
RadioTower tower = NpcPlugin.settlementMap.get(world).getRadioTower();
|
||||
|
||||
if (tower != null && Util.manhattanDist(
|
||||
tower.getWorld().getX(), tower.getWorld().getY(),
|
||||
cubot.getWorld().getX(), cubot.getWorld().getY()) <= RadioTower.MAX_RANGE) {
|
||||
//Tower is in range
|
||||
messages.addAll(tower.getMessages());
|
||||
}
|
||||
}
|
||||
|
||||
//Write messages to memory
|
||||
int offset = 0;
|
||||
|
||||
for (char[] message : messages) {
|
||||
|
||||
getCpu().getMemory().write(x + offset, message, 0, message.length);
|
||||
offset += message.length;
|
||||
}
|
||||
|
||||
//Write the amount of messages received to B
|
||||
getCpu().getRegisterSet().getRegister("B").setValue(messages.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getId() {
|
||||
return HWID;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
|
||||
Document dbObject = new Document();
|
||||
|
||||
dbObject.put("type", getClass().getCanonicalName());
|
||||
dbObject.put("cubot", cubot.getObjectId());
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
}
|
||||
79
src/main/java/net/simon987/mar/npcplugin/RadioTower.java
Normal file
79
src/main/java/net/simon987/mar/npcplugin/RadioTower.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.MessageReceiver;
|
||||
import net.simon987.mar.server.game.objects.Structure;
|
||||
import net.simon987.mar.server.game.objects.Updatable;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RadioTower extends Structure implements MessageReceiver, Updatable {
|
||||
|
||||
private static final int MAP_INFO = 0x0901;
|
||||
|
||||
public static final int MAX_RANGE = GameServer.INSTANCE.getConfig().getInt("radio_tower_range");
|
||||
|
||||
private static final int MAX_MESSAGES = 16;
|
||||
|
||||
public RadioTower() {
|
||||
super(1, 1);
|
||||
}
|
||||
|
||||
public RadioTower(Document document) {
|
||||
super(document, 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Messages from the current tick
|
||||
*/
|
||||
private final ArrayList<char[]> messages = new ArrayList<>(4);
|
||||
|
||||
/**
|
||||
* Messages from the last tick
|
||||
*/
|
||||
private ArrayList<char[]> lastMessages = new ArrayList<>(4);
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
lastMessages = new ArrayList<>(messages);
|
||||
messages.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMessage(char[] message) {
|
||||
|
||||
if (message.length < MAX_MESSAGES) {
|
||||
messages.add(message);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<char[]> getMessages() {
|
||||
return lastMessages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
JSONObject json = super.debugJsonSerialise();
|
||||
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
for (char[] message : this.messages) {
|
||||
messages.add(new String(message));
|
||||
}
|
||||
|
||||
json.put("messages", messages);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
222
src/main/java/net/simon987/mar/npcplugin/Settlement.java
Normal file
222
src/main/java/net/simon987/mar/npcplugin/Settlement.java
Normal file
@@ -0,0 +1,222 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.world.TilePlain;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import net.simon987.mar.server.game.world.WorldGenerationException;
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Settlement implements MongoSerializable {
|
||||
|
||||
private Factory factory = null;
|
||||
private RadioTower radioTower = null;
|
||||
private VaultDoor vaultDoor = null;
|
||||
private final World world;
|
||||
private final DifficultyLevel difficultyLevel;
|
||||
|
||||
private final List<NonPlayerCharacter> npcs = new ArrayList<>();
|
||||
|
||||
private final char[] password;
|
||||
|
||||
public Settlement(Document document) {
|
||||
|
||||
world = GameServer.INSTANCE.getGameUniverse().getWorld(document.getString("world"), false);
|
||||
ObjectId radioTowerId = document.getObjectId("radio_tower");
|
||||
if (radioTowerId != null) {
|
||||
radioTower = (RadioTower) GameServer.INSTANCE.getGameUniverse().getObject(radioTowerId);
|
||||
}
|
||||
ObjectId vaultDoorId = document.getObjectId("vault_door");
|
||||
if (vaultDoorId != null) {
|
||||
vaultDoor = (VaultDoor) GameServer.INSTANCE.getGameUniverse().getObject(vaultDoorId);
|
||||
}
|
||||
ObjectId factoryId = document.getObjectId("factory");
|
||||
factory = (Factory) GameServer.INSTANCE.getGameUniverse().getObject(factoryId);
|
||||
|
||||
difficultyLevel = DifficultyLevel.values()[document.getInteger("difficulty_level")];
|
||||
|
||||
Object[] npcArray = ((ArrayList) document.get("npcs")).toArray();
|
||||
for (Object id : npcArray) {
|
||||
|
||||
NonPlayerCharacter npc = (NonPlayerCharacter) GameServer.INSTANCE.getGameUniverse().getObject((ObjectId) id);
|
||||
|
||||
if (npc != null) {
|
||||
addNpc(npc);
|
||||
}
|
||||
}
|
||||
|
||||
password = document.getString("password").toCharArray();
|
||||
}
|
||||
|
||||
public Settlement(World world) throws WorldGenerationException {
|
||||
|
||||
this.world = world;
|
||||
this.difficultyLevel = DifficultyLevel.NORMAL; //TODO randomize ?
|
||||
this.password = "12345678".toCharArray();
|
||||
|
||||
outerLoopFactory:
|
||||
for (int x = 2; x < 12; x++) {
|
||||
for (int y = 2; y < 12; y++) {
|
||||
|
||||
if ((!world.isTileBlocked(x, y) && !world.isTileBlocked(x + 1, y) &&
|
||||
!world.isTileBlocked(x, y + 1) && !world.isTileBlocked(x + 1, y + 1))) {
|
||||
|
||||
Factory factory = new Factory();
|
||||
|
||||
factory.setWorld(world);
|
||||
factory.setObjectId(new ObjectId());
|
||||
factory.setX(x);
|
||||
factory.setY(y);
|
||||
|
||||
if (factory.getAdjacentTile() == null) {
|
||||
//Factory has no non-blocked adjacent tiles
|
||||
continue;
|
||||
}
|
||||
|
||||
world.addObject(factory);
|
||||
world.incUpdatable();
|
||||
this.factory = factory;
|
||||
|
||||
break outerLoopFactory;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (factory == null) {
|
||||
throw new WorldGenerationException("Could not place Factory");
|
||||
}
|
||||
|
||||
//Also spawn a radio tower in the same World
|
||||
Point p = world.getRandomTileWithAdjacent(8, TilePlain.ID);
|
||||
if (p != null) {
|
||||
while (p.x == 0 || p.x == world.getWorldSize() - 1 || p.y == world.getWorldSize() - 1 || p.y == 0) {
|
||||
p = world.getRandomPassableTile();
|
||||
|
||||
if (p == null) {
|
||||
//World is full
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RadioTower radioTower = new RadioTower();
|
||||
|
||||
radioTower.setWorld(world);
|
||||
radioTower.setObjectId(new ObjectId());
|
||||
radioTower.setX(p.x);
|
||||
radioTower.setY(p.y);
|
||||
|
||||
if (radioTower.getAdjacentTile() != null) {
|
||||
//Radio Tower has adjacent tiles
|
||||
world.addObject(radioTower);
|
||||
world.incUpdatable(); //In case the Factory couldn't be spawned.
|
||||
|
||||
this.radioTower = radioTower;
|
||||
}
|
||||
}
|
||||
|
||||
//Also spawn a Vault in the same World
|
||||
p = world.getRandomPassableTile();
|
||||
if (p != null) {
|
||||
|
||||
VaultDoor vaultDoor = new VaultDoor();
|
||||
vaultDoor.setWorld(world);
|
||||
|
||||
int counter = 700;
|
||||
while (p.x == 0 || p.x == world.getWorldSize() - 1 || p.y == world.getWorldSize() - 1 || p.y == 0
|
||||
|| vaultDoor.getAdjacentTileCount(true) < 8) {
|
||||
p = world.getRandomPassableTile();
|
||||
|
||||
if (p == null) {
|
||||
//World is full
|
||||
return;
|
||||
}
|
||||
|
||||
vaultDoor.setX(p.x);
|
||||
vaultDoor.setY(p.y);
|
||||
|
||||
counter--;
|
||||
|
||||
if (counter <= 0) {
|
||||
//Reached maximum amount of retries
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vaultDoor.setObjectId(new ObjectId());
|
||||
world.addObject(vaultDoor);
|
||||
world.incUpdatable(); //In case the Factory & Radio Tower couldn't be spawned.
|
||||
vaultDoor.setWorld(world);
|
||||
|
||||
vaultDoor.initialize();
|
||||
this.vaultDoor = vaultDoor;
|
||||
}
|
||||
}
|
||||
|
||||
public void addNpc(NonPlayerCharacter npc) {
|
||||
npcs.add(npc);
|
||||
npc.setSettlement(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = new Document();
|
||||
|
||||
document.put("world", world.getId());
|
||||
if (radioTower != null) {
|
||||
document.put("radio_tower", radioTower.getObjectId());
|
||||
}
|
||||
if (vaultDoor != null) {
|
||||
document.put("vault_door", vaultDoor.getObjectId());
|
||||
}
|
||||
document.put("factory", factory.getObjectId());
|
||||
document.put("difficulty_level", difficultyLevel.ordinal());
|
||||
document.put("password", String.valueOf(password));
|
||||
|
||||
|
||||
List<ObjectId> npcArray = new ArrayList<>(npcs.size());
|
||||
for (NonPlayerCharacter npc : npcs) {
|
||||
npcArray.add(npc.getObjectId());
|
||||
}
|
||||
document.put("npcs", npcArray);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
public enum DifficultyLevel {
|
||||
NORMAL(0);
|
||||
|
||||
public int cypherId;
|
||||
|
||||
DifficultyLevel(int cypherId) {
|
||||
this.cypherId = cypherId;
|
||||
}
|
||||
}
|
||||
|
||||
public Factory getFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
public RadioTower getRadioTower() {
|
||||
return radioTower;
|
||||
}
|
||||
|
||||
public VaultDoor getVaultDoor() {
|
||||
return vaultDoor;
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public List<NonPlayerCharacter> getNpcs() {
|
||||
return npcs;
|
||||
}
|
||||
|
||||
public char[] getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
261
src/main/java/net/simon987/mar/npcplugin/VaultDimension.java
Normal file
261
src/main/java/net/simon987/mar/npcplugin/VaultDimension.java
Normal file
@@ -0,0 +1,261 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.npcplugin.world.TileVaultFloor;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.game.objects.Direction;
|
||||
import net.simon987.mar.server.game.world.Location;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Random;
|
||||
|
||||
public class VaultDimension {
|
||||
|
||||
/**
|
||||
* Name of the dimension
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
private World homeWorld;
|
||||
|
||||
private int homeX;
|
||||
private int homeY;
|
||||
|
||||
public VaultDimension(VaultDoor vaultDoor) {
|
||||
|
||||
name = "v" + vaultDoor.getObjectId();
|
||||
|
||||
/*
|
||||
* Creates a group of vault worlds and pieces them together with openings.
|
||||
* For a set number of passes, a random number of vault worlds are added to each world in the
|
||||
* previous 'layer' of worlds in a random direction. Openings are added to allow movement from a
|
||||
* layer to the next, meaning that adjacent worlds are not necessarily connected, and one would
|
||||
* necessarily need to travel through 5 openings to reach the 6th layer, even when that layer is
|
||||
* less than 5 worlds away from the origin/home vault world (the one containing the exit door).
|
||||
*
|
||||
* 1. Create home world (layer 0)
|
||||
* 2. For each world in the current layer, attach a random number of new worlds
|
||||
* 3. Repeat the same step for the newly added layer
|
||||
* 4. Choose a random world from the last layer and create the vault box there (objective)
|
||||
* 5. Create an exit portal in the home world
|
||||
*
|
||||
* This process is done in 2 passes, in the first pass, worlds are defined
|
||||
* as a set of coordinates + a list of opening directions, then they are actually generated
|
||||
*/
|
||||
|
||||
IServerConfiguration config = GameServer.INSTANCE.getConfig();
|
||||
|
||||
int minLayerCount = config.getInt("vault_wg_min_layer_count");
|
||||
int maxLayerCount = config.getInt("vault_wg_max_layer_count");
|
||||
int minAttachedWorld = config.getInt("vault_wg_min_attached_world");
|
||||
int maxAttachedWorld = Math.min(config.getInt("vault_wg_max_attached_world"), 4);
|
||||
int minElectricBoxCount = config.getInt("vault_wg_min_electric_box_count");
|
||||
int maxElectricBoxCount = config.getInt("vault_wg_max_electric_box_count");
|
||||
|
||||
HashMap<Integer, ArrayList<WorldBluePrint>> worldLayers = new HashMap<>();
|
||||
VaultWorldGenerator generator = new VaultWorldGenerator();
|
||||
Random random = new Random();
|
||||
|
||||
int layerCount = random.nextInt(maxLayerCount - minLayerCount) + minLayerCount;
|
||||
|
||||
//1. Create home world
|
||||
WorldBluePrint homeWorldBluePrint = new WorldBluePrint();
|
||||
homeWorldBluePrint.coords.x = 0x7FFF;
|
||||
homeWorldBluePrint.coords.y = 0x7FFF;
|
||||
worldLayers.put(0, new ArrayList<>());
|
||||
worldLayers.get(0).add(homeWorldBluePrint);
|
||||
|
||||
//2. For each world in the current layer, attach a random number of new worlds
|
||||
for (int i = 1; i <= layerCount; i++) {
|
||||
|
||||
worldLayers.put(i, new ArrayList<>());
|
||||
|
||||
for (WorldBluePrint world : worldLayers.get(i - 1)) {
|
||||
|
||||
int attachedWorlds;
|
||||
if (i == 1) {
|
||||
attachedWorlds = 4; // The home world should have 4 attached worlds
|
||||
} else {
|
||||
attachedWorlds = random.nextInt(maxAttachedWorld - minAttachedWorld) + minAttachedWorld;
|
||||
}
|
||||
|
||||
for (int j = 0; j < attachedWorlds; j++) {
|
||||
|
||||
int randDirIndex = random.nextInt(4);
|
||||
|
||||
//Try 4 directions (wrap around 0..3)
|
||||
for (int attemptCount = 0; attemptCount < 4; attemptCount++) {
|
||||
Direction randomDirection = Direction.getDirection(randDirIndex);
|
||||
|
||||
//Don't attach a world at the same spot twice
|
||||
if (!worldExists(world.coordinatesOf(randomDirection), worldLayers)) {
|
||||
WorldBluePrint attachedWorld = world.attachWorld(randomDirection);
|
||||
worldLayers.get(i).add(attachedWorld);
|
||||
}
|
||||
randDirIndex = (randDirIndex + 1) % 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<World> lastLayerWorlds = new ArrayList<>();
|
||||
|
||||
//Generate worlds
|
||||
for (Integer key : worldLayers.keySet()) {
|
||||
|
||||
ArrayList<WorldBluePrint> layer = worldLayers.get(key);
|
||||
|
||||
for (WorldBluePrint bp : layer) {
|
||||
World vWorld = generator.generateVaultWorld(bp.coords.x, bp.coords.y, bp.openings, name);
|
||||
GameServer.INSTANCE.getGameUniverse().addWorld(vWorld);
|
||||
|
||||
ArrayList<ElectricBox> newBoxes = VaultWorldUtils.generateElectricBoxes(vWorld, minElectricBoxCount,
|
||||
maxElectricBoxCount);
|
||||
for (ElectricBox blob : newBoxes) {
|
||||
vWorld.addObject(blob);
|
||||
vWorld.incUpdatable();
|
||||
}
|
||||
|
||||
if (key == layerCount) {
|
||||
lastLayerWorlds.add(vWorld);
|
||||
}
|
||||
|
||||
if (key == 0) {
|
||||
this.homeWorld = vWorld;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point exitCoords = vaultDoor.getAdjacentTile();
|
||||
Location exitLocation = new Location(vaultDoor.getWorld().getX(), vaultDoor.getWorld().getY(), vaultDoor
|
||||
.getWorld().getDimension(), exitCoords.x, exitCoords.y);
|
||||
|
||||
|
||||
//4. Choose a random world from the last layer and create the vault box there (objective)
|
||||
World objectiveWorld = lastLayerWorlds.get(random.nextInt(lastLayerWorlds.size()));
|
||||
|
||||
Point exitPortalPt = objectiveWorld.getRandomTileWithAdjacent(8, TileVaultFloor.ID);
|
||||
|
||||
if (exitPortalPt != null) {
|
||||
|
||||
VaultExitPortal exitPortal = new VaultExitPortal();
|
||||
exitPortal.setDestination(exitLocation);
|
||||
exitPortal.setX(exitPortalPt.x);
|
||||
exitPortal.setY(exitPortalPt.y);
|
||||
exitPortal.setWorld(objectiveWorld);
|
||||
exitPortal.setObjectId(new ObjectId());
|
||||
objectiveWorld.addObject(exitPortal);
|
||||
|
||||
// LogManager.LOGGER.severe("Objective: " + objectiveWorld.getId());
|
||||
|
||||
} else {
|
||||
LogManager.LOGGER.severe("FIXME: Couldn't create exit portal for world " + homeWorld.getId());
|
||||
}
|
||||
|
||||
//5. Create an exit portal in the home World
|
||||
Point homePortalPt = homeWorld.getRandomTileWithAdjacent(8, TileVaultFloor.ID);
|
||||
if (homePortalPt != null) {
|
||||
|
||||
Portal homePortal = new Portal();
|
||||
homePortal.setDestination(exitLocation);
|
||||
homePortal.setX(homePortalPt.x);
|
||||
homePortal.setY(homePortalPt.y);
|
||||
homePortal.setWorld(homeWorld);
|
||||
homePortal.setObjectId(new ObjectId());
|
||||
homeWorld.addObject(homePortal);
|
||||
|
||||
Point entryCoords = homePortal.getAdjacentTile();
|
||||
homeX = entryCoords.x;
|
||||
homeY = entryCoords.y;
|
||||
|
||||
} else {
|
||||
LogManager.LOGGER.severe("FIXME: Couldn't create home exit portal for world " + homeWorld.getId());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private boolean worldExists(Point coords, HashMap<Integer, ArrayList<WorldBluePrint>> worldLayers) {
|
||||
|
||||
//Auto-generated by IntelliJ Idea
|
||||
return worldLayers.values().stream().flatMap(Collection::stream).anyMatch(bp -> bp.coords.equals(coords));
|
||||
}
|
||||
|
||||
World getHomeWorld() {
|
||||
return homeWorld;
|
||||
}
|
||||
|
||||
public int getHomeX() {
|
||||
return homeX;
|
||||
}
|
||||
|
||||
public int getHomeY() {
|
||||
return homeY;
|
||||
}
|
||||
/**
|
||||
* Helper class to plan the layout of a vault dimension
|
||||
*/
|
||||
private class WorldBluePrint {
|
||||
|
||||
ArrayList<Direction> openings = new ArrayList<>(4);
|
||||
|
||||
/**
|
||||
* Coordinates of the world
|
||||
*/
|
||||
Point coords = new Point();
|
||||
|
||||
/**
|
||||
* Update the blueprint's openings to allow traveling to the newly attached world
|
||||
*
|
||||
* @param direction direction of the world to attach (relative to this one)
|
||||
* @return The blueprint of the attached world
|
||||
*/
|
||||
WorldBluePrint attachWorld(Direction direction) {
|
||||
|
||||
WorldBluePrint attachedWorld = new WorldBluePrint();
|
||||
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
openings.add(Direction.NORTH);
|
||||
attachedWorld.openings.add(Direction.SOUTH);
|
||||
break;
|
||||
case EAST:
|
||||
openings.add(Direction.EAST);
|
||||
attachedWorld.openings.add(Direction.WEST);
|
||||
break;
|
||||
case SOUTH:
|
||||
openings.add(Direction.SOUTH);
|
||||
attachedWorld.openings.add(Direction.NORTH);
|
||||
break;
|
||||
case WEST:
|
||||
openings.add(Direction.WEST);
|
||||
attachedWorld.openings.add(Direction.EAST);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
attachedWorld.coords.x = coords.x + direction.dX;
|
||||
attachedWorld.coords.y = coords.y + direction.dY;
|
||||
|
||||
return attachedWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the coordinates of a world that would be attached to this world
|
||||
*
|
||||
* @param direction direction of the attached world
|
||||
*/
|
||||
Point coordinatesOf(Direction direction) {
|
||||
|
||||
return new Point(coords.x + direction.dX, coords.y + direction.dY);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
164
src/main/java/net/simon987/mar/npcplugin/VaultDoor.java
Normal file
164
src/main/java/net/simon987/mar/npcplugin/VaultDoor.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.*;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class VaultDoor extends Structure implements MessageReceiver, Enterable, Updatable {
|
||||
|
||||
private static final int MAP_INFO = 0x0B00;
|
||||
|
||||
/**
|
||||
* Whether or not the vault door is opened
|
||||
*/
|
||||
private boolean open = false;
|
||||
|
||||
private int homeX;
|
||||
private int homeY;
|
||||
private World homeWorld;
|
||||
|
||||
|
||||
/**
|
||||
* Number of ticks to remain the door open
|
||||
*/
|
||||
private final int OPEN_TIME = GameServer.INSTANCE.getConfig().getInt("vault_door_open_time");
|
||||
|
||||
private int openedTimer = 0;
|
||||
|
||||
public VaultDoor() {
|
||||
super(1, 1);
|
||||
}
|
||||
|
||||
public VaultDoor(Document document) {
|
||||
super(document, 1, 1);
|
||||
|
||||
setX(document.getInteger("x"));
|
||||
setY(document.getInteger("y"));
|
||||
|
||||
|
||||
if (document.containsKey("homeX") && document.containsKey("homeY")) {
|
||||
homeX = document.getInteger("homeX");
|
||||
homeY = document.getInteger("homeY");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
if (open){
|
||||
if (openedTimer <= 0) {
|
||||
//Door was open for OPEN_TIME, close it
|
||||
open = false;
|
||||
openedTimer = 0;
|
||||
LogManager.LOGGER.fine("Closed Vault door ID: " + getObjectId());
|
||||
} else {
|
||||
openedTimer--;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMessage(char[] message) {
|
||||
|
||||
Settlement settlement = NpcPlugin.settlementMap.get(getWorld().getId());
|
||||
|
||||
System.out.println("message: " + new String(message));
|
||||
System.out.println("password: " + new String(settlement.getPassword()));
|
||||
|
||||
if (Arrays.equals(message, settlement.getPassword())) {
|
||||
if (!open) {
|
||||
openVault();
|
||||
} else {
|
||||
keepVaultOpen();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void openVault() {
|
||||
open = true;
|
||||
openedTimer = OPEN_TIME;
|
||||
LogManager.LOGGER.fine("Opened Vault door ID: " + getObjectId());
|
||||
}
|
||||
|
||||
private void keepVaultOpen() {
|
||||
open = true;
|
||||
openedTimer = OPEN_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enter(GameObject object) {
|
||||
|
||||
// LogManager.LOGGER.fine("VAULT enter " + open);
|
||||
|
||||
if (open) {
|
||||
|
||||
object.getWorld().decUpdatable();
|
||||
object.getWorld().removeObject(object);
|
||||
|
||||
homeWorld.incUpdatable();
|
||||
homeWorld.addObject(object);
|
||||
object.setWorld(homeWorld);
|
||||
object.setX(homeX);
|
||||
object.setY(homeY);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = super.mongoSerialise();
|
||||
|
||||
dbObject.put("homeX", getHomeX());
|
||||
dbObject.put("homeY", getHomeY());
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
//Get or generate vault world
|
||||
homeWorld = GameServer.INSTANCE.getGameUniverse().getWorld(0x7FFF, 0x7FFF,
|
||||
false, "v" + getObjectId() + "-");
|
||||
|
||||
if (homeWorld == null) {
|
||||
VaultDimension vaultDimension = new VaultDimension(this);
|
||||
homeWorld = vaultDimension.getHomeWorld();
|
||||
homeX = vaultDimension.getHomeX();
|
||||
homeY = vaultDimension.getHomeY();
|
||||
}
|
||||
}
|
||||
|
||||
public int getHomeX() {
|
||||
return homeX;
|
||||
}
|
||||
|
||||
public void setHomeX(int homeX) {
|
||||
this.homeX = homeX;
|
||||
}
|
||||
|
||||
public int getHomeY() {
|
||||
return homeY;
|
||||
}
|
||||
|
||||
public void setHomeY(int homeY) {
|
||||
this.homeY = homeY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.world.Location;
|
||||
import org.bson.Document;
|
||||
|
||||
/**
|
||||
* Final exit portal located in the 'last' World of a Vault dimension
|
||||
*/
|
||||
public class VaultExitPortal extends Portal {
|
||||
|
||||
public VaultExitPortal() {
|
||||
|
||||
}
|
||||
|
||||
public VaultExitPortal(Document document) {
|
||||
super(document);
|
||||
|
||||
setDestination(new Location(
|
||||
document.getInteger("dstWorldX"),
|
||||
document.getInteger("dstWorldY"),
|
||||
document.getString("dstDimension"),
|
||||
document.getInteger("dstX"),
|
||||
document.getInteger("dstY")));
|
||||
setX(document.getInteger("x"));
|
||||
setY(document.getInteger("y"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enter(GameObject object) {
|
||||
|
||||
//TODO: Trigger vault complete event instead
|
||||
return super.enter(object);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.server.game.objects.Direction;
|
||||
import net.simon987.mar.server.game.world.TileMap;
|
||||
import net.simon987.mar.server.game.world.TileVoid;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Random;
|
||||
|
||||
public class VaultWorldGenerator {
|
||||
|
||||
public World generateVaultWorld(int worldX, int worldY, ArrayList<Direction> openings, String dimension) {
|
||||
|
||||
// LogManager.LOGGER.info("Generating vault World");
|
||||
|
||||
/*
|
||||
* Openings are always at the same spot (marked by '?')
|
||||
* 1. Rectangles of random size are generated
|
||||
* 2. Openings are generated (if another vault world is connected on this direction)
|
||||
* 3. They are connected with tunnels from bottom to top,
|
||||
* by their roomCenter, alternating between horizontal first
|
||||
* tunnels and vertical first
|
||||
* 4. Tiles adjacent to floor tiles are set to wall tiles
|
||||
* Example map:
|
||||
*
|
||||
* # # # # # # # - - - # ? ? # - - - - - - - - - -
|
||||
* # o o o o o # - - - # ? ? # - - - - - - - - - -
|
||||
* # o o o o o # # # # # # o # - - - - - - - - - -
|
||||
* # o o o o o o o o o o o o # - - - - - - - - - -
|
||||
* # o o o o o # # # # # # # # # # # - - - - - - -
|
||||
* # o o o o o # - - - - # o o o o # - - - - - - -
|
||||
* # o o o o o # # # # # # o o o o # - - - - - - -
|
||||
* # # o o o o o o o o o o o o o o # - - - - - - -
|
||||
* - # o # # # # # # # # # o o o o # - - - - - - -
|
||||
* - # o # - - - - - - - # # # # # # - - - - - - -
|
||||
* # # o # - - - - - - - - - - - - - - - - - # # #
|
||||
* ? ? o # # # # # # # # # # # # # # # # # # # ? ?
|
||||
* ? ? o o o o o o o o o o o o o o o o o o o o ? ?
|
||||
* # # # # # # # # # # # # # # # # # # o # # # # #
|
||||
* - - - - - - - - - - - - - - - - - # o # - - - -
|
||||
* - # # # # # # # # # - - - - - - - # o # - - - -
|
||||
* - # o o o o o o o # - - - # # # # # o # # # # -
|
||||
* - # o o o o o o o # # # # # o o o o o o o o # -
|
||||
* - # o o o o o o o o o o o # o o o o o o o o # -
|
||||
* - # o o o o o o o # # # o # o o o o o o o o # -
|
||||
* - # o o o o o o o # - # o # # # # # # # # # # -
|
||||
* - # o o o o o o o # # # o # - - - - - - - - - -
|
||||
* - # # # # # # # # # # ? ? # - - - - - - - - - -
|
||||
* - - - - - - - - - - # ? ? # - - - - - - - - - -
|
||||
*/
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
int worldSize = 20;
|
||||
int floorTile = 4;
|
||||
int wallTile = 5;
|
||||
int minRoomCount = 5;
|
||||
int maxRoomCount = 8;
|
||||
int minRoomWidth = 4;
|
||||
int minRoomHeight = 4;
|
||||
int maxRoomWidth = 8;
|
||||
int maxRoomHeight = 8;
|
||||
|
||||
ArrayList<Point> roomCenters = new ArrayList<>(maxRoomCount + 4);
|
||||
ArrayList<Rectangle> rooms = new ArrayList<>(maxRoomCount);
|
||||
|
||||
World world = new World(worldX, worldY, new TileMap(worldSize, worldSize), dimension);
|
||||
|
||||
TileMap map = world.getTileMap();
|
||||
|
||||
//Generate rectangles of random size
|
||||
int roomCount = random.nextInt(maxRoomCount - minRoomCount) + minRoomCount;
|
||||
|
||||
for (int i = 0; i < roomCount; i++) {
|
||||
|
||||
int roomWidth = random.nextInt(maxRoomWidth - minRoomWidth) + minRoomWidth;
|
||||
int roomHeight = random.nextInt(maxRoomHeight - minRoomHeight) + minRoomHeight;
|
||||
|
||||
int attempts = 35;
|
||||
Point roomCorner;
|
||||
Rectangle room = null;
|
||||
|
||||
//Try to find a spot that doesn't intersect another room
|
||||
attemptsLoop:
|
||||
while (true) {
|
||||
|
||||
if (attempts < 0) {
|
||||
roomCorner = null;
|
||||
break;
|
||||
}
|
||||
|
||||
roomCorner = new Point(random.nextInt((worldSize - roomWidth) - 2) + 2,
|
||||
random.nextInt((worldSize - roomHeight) - 2) + 2);
|
||||
|
||||
room = new Rectangle(roomCorner.x, roomCorner.y, roomWidth, roomHeight);
|
||||
|
||||
for (Rectangle otherRoom : rooms) {
|
||||
if (otherRoom.intersects(room)) {
|
||||
attempts--;
|
||||
continue attemptsLoop;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (roomCorner != null) {
|
||||
for (int x = roomCorner.x; x < roomCorner.x + roomWidth; x++) {
|
||||
for (int y = roomCorner.y; y < roomCorner.y + roomHeight; y++) {
|
||||
map.setTileAt(floorTile, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
rooms.add(room);
|
||||
roomCenters.add(new Point(roomCorner.x + roomWidth / 2, roomCorner.y + roomHeight / 2));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Create openings
|
||||
for (Direction opening : openings) {
|
||||
switch (opening) {
|
||||
case NORTH:
|
||||
|
||||
map.setTileAt(floorTile, worldSize / 2, 0);
|
||||
map.setTileAt(floorTile, worldSize / 2, 1);
|
||||
map.setTileAt(floorTile, worldSize / 2 - 1, 0);
|
||||
map.setTileAt(floorTile, worldSize / 2 - 1, 1);
|
||||
roomCenters.add(new Point(worldSize / 2, 1));
|
||||
break;
|
||||
case EAST:
|
||||
|
||||
map.setTileAt(floorTile, worldSize - 1, worldSize / 2);
|
||||
map.setTileAt(floorTile, worldSize - 1, worldSize / 2 - 1);
|
||||
map.setTileAt(floorTile, worldSize - 2, worldSize / 2);
|
||||
map.setTileAt(floorTile, worldSize - 2, worldSize / 2 - 1);
|
||||
roomCenters.add(new Point(worldSize - 2, worldSize / 2 - 1));
|
||||
break;
|
||||
case SOUTH:
|
||||
|
||||
map.setTileAt(floorTile, worldSize / 2, worldSize - 1);
|
||||
map.setTileAt(floorTile, worldSize / 2, worldSize - 2);
|
||||
map.setTileAt(floorTile, worldSize / 2 - 1, worldSize - 1);
|
||||
map.setTileAt(floorTile, worldSize / 2 - 1, worldSize - 2);
|
||||
roomCenters.add(new Point(worldSize / 2, worldSize - 2));
|
||||
break;
|
||||
case WEST:
|
||||
|
||||
map.setTileAt(floorTile, 0, worldSize / 2);
|
||||
map.setTileAt(floorTile, 0, worldSize / 2 - 1);
|
||||
map.setTileAt(floorTile, 1, worldSize / 2);
|
||||
map.setTileAt(floorTile, 1, worldSize / 2 - 1);
|
||||
roomCenters.add(new Point(1, worldSize / 2 - 1));
|
||||
break;
|
||||
default:
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Connect rooms together, from bottom to top
|
||||
roomCenters.sort(new RoomCenterComparator());
|
||||
boolean xFirst = true; //Start the tunnel horizontally
|
||||
|
||||
for (int i = 0; i < roomCenters.size() - 1; i++) {
|
||||
//Note to self: I wouldn't bother trying to understand what's in this for loop,
|
||||
//If this needs to be modified just trash it and start over
|
||||
|
||||
if (xFirst) {
|
||||
if (roomCenters.get(i + 1).x - roomCenters.get(i).x < 0) {
|
||||
for (int x = roomCenters.get(i).x; x > roomCenters.get(i + 1).x; x--) {
|
||||
map.setTileAt(floorTile, x, roomCenters.get(i).y);
|
||||
}
|
||||
} else {
|
||||
for (int x = roomCenters.get(i).x; x < roomCenters.get(i + 1).x; x++) {
|
||||
map.setTileAt(floorTile, x, roomCenters.get(i).y);
|
||||
}
|
||||
}
|
||||
|
||||
if (roomCenters.get(i + 1).y - roomCenters.get(i).y < 0) {
|
||||
for (int y = roomCenters.get(i).y; y > roomCenters.get(i + 1).y; y--) {
|
||||
map.setTileAt(floorTile, roomCenters.get(i + 1).x, y);
|
||||
}
|
||||
} else {
|
||||
for (int y = roomCenters.get(i).y; y < roomCenters.get(i + 1).y; y++) {
|
||||
map.setTileAt(floorTile, roomCenters.get(i + 1).x, y);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
if (roomCenters.get(i + 1).x - roomCenters.get(i).x < 0) {
|
||||
for (int x = roomCenters.get(i).x; x > roomCenters.get(i + 1).x; x--) {
|
||||
map.setTileAt(floorTile, x, roomCenters.get(i + 1).y);
|
||||
}
|
||||
} else {
|
||||
for (int x = roomCenters.get(i).x; x < roomCenters.get(i + 1).x; x++) {
|
||||
map.setTileAt(floorTile, x, roomCenters.get(i + 1).y);
|
||||
}
|
||||
}
|
||||
|
||||
if (roomCenters.get(i + 1).y - roomCenters.get(i).y < 0) {
|
||||
for (int y = roomCenters.get(i).y; y > roomCenters.get(i + 1).y; y--) {
|
||||
map.setTileAt(floorTile, roomCenters.get(i).x, y);
|
||||
}
|
||||
} else {
|
||||
for (int y = roomCenters.get(i).y; y < roomCenters.get(i + 1).y; y++) {
|
||||
map.setTileAt(floorTile, roomCenters.get(i).x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xFirst = !xFirst;
|
||||
}
|
||||
|
||||
//Tiles adjacent to floor tiles are set to wall tiles
|
||||
for (int x = 0; x < worldSize; x++) {
|
||||
for (int y = 0; y < worldSize; y++) {
|
||||
|
||||
if (map.getTileIdAt(x, y) != floorTile && hasTileAdjacent(x, y, map, floorTile)) {
|
||||
map.setTileAt(wallTile, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Set other tiles to 'void'
|
||||
for (int x = 0; x < worldSize; x++) {
|
||||
for (int y = 0; y < worldSize; y++) {
|
||||
|
||||
if (map.getTileIdAt(x, y) != floorTile && map.getTileIdAt(x, y) != wallTile) {
|
||||
map.setTileAt(new TileVoid(), x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return world;
|
||||
|
||||
}
|
||||
|
||||
private boolean hasTileAdjacent(int x, int y, TileMap map, int tile) {
|
||||
|
||||
for (int dX = -1; dX <= 1; dX++) {
|
||||
for (int dY = -1; dY <= 1; dY++) {
|
||||
|
||||
if (map.getTileIdAt(x + dX, y + dY) == tile) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class RoomCenterComparator implements Comparator<Point> {
|
||||
@Override
|
||||
public int compare(Point o1, Point o2) {
|
||||
return o1.y - o2.y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package net.simon987.mar.npcplugin;
|
||||
|
||||
import net.simon987.mar.npcplugin.world.TileVaultFloor;
|
||||
import net.simon987.mar.server.game.world.TileMap;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
public class VaultWorldUtils {
|
||||
|
||||
|
||||
public static ArrayList<ElectricBox> generateElectricBoxes(World world, int minCount, int maxCount) {
|
||||
|
||||
Random random = new Random();
|
||||
int boxesCount = random.nextInt(maxCount - minCount) + minCount;
|
||||
ArrayList<ElectricBox> electricBoxes = new ArrayList<>(boxesCount);
|
||||
|
||||
//Count number of floor tiles. If there is less plain tiles than desired amount of boxes,
|
||||
//set the desired amount of blobs to the plain tile count
|
||||
TileMap m = world.getTileMap();
|
||||
int floorCount = 0;
|
||||
for (int y = 0; y < world.getWorldSize(); y++) {
|
||||
for (int x = 0; x < world.getWorldSize(); x++) {
|
||||
|
||||
if (m.getTileIdAt(x, y) == TileVaultFloor.ID) {
|
||||
floorCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (boxesCount > floorCount) {
|
||||
boxesCount = floorCount;
|
||||
}
|
||||
|
||||
outerLoop:
|
||||
for (int i = 0; i < boxesCount; i++) {
|
||||
|
||||
Point p = m.getRandomTile(TileVaultFloor.ID);
|
||||
if (p != null) {
|
||||
|
||||
//Don't block worlds
|
||||
int counter = 0;
|
||||
while (p.x == 0 || p.y == 0 || p.x == world.getWorldSize() - 1 || p.y == world.getWorldSize() - 1 ||
|
||||
world.getGameObjectsAt(p.x, p.y).size() != 0) {
|
||||
p = m.getRandomTile(TileVaultFloor.ID);
|
||||
counter++;
|
||||
|
||||
if (counter > 25) {
|
||||
continue outerLoop;
|
||||
}
|
||||
}
|
||||
|
||||
for (ElectricBox box : electricBoxes) {
|
||||
if (box.getX() == p.x && box.getY() == p.y) {
|
||||
//There is already a box here
|
||||
continue outerLoop;
|
||||
}
|
||||
}
|
||||
|
||||
ElectricBox box = new ElectricBox();
|
||||
box.setObjectId(new ObjectId());
|
||||
box.setX(p.x);
|
||||
box.setY(p.y);
|
||||
box.setWorld(world);
|
||||
|
||||
electricBoxes.add(box);
|
||||
}
|
||||
}
|
||||
|
||||
return electricBoxes;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.simon987.mar.npcplugin.event;
|
||||
|
||||
import net.simon987.mar.npcplugin.RadioReceiverHardware;
|
||||
import net.simon987.mar.server.assembly.CPU;
|
||||
import net.simon987.mar.server.event.CpuInitialisationEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
|
||||
public class CpuInitialisationListener implements GameEventListener {
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return CpuInitialisationEvent.class;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
CPU cpu = (CPU) event.getSource();
|
||||
ControllableUnit controllableUnit = ((CpuInitialisationEvent) event).getUnit();
|
||||
cpu.setHardwareHost(controllableUnit);
|
||||
|
||||
RadioReceiverHardware radioHw = new RadioReceiverHardware(controllableUnit);
|
||||
radioHw.setCpu(cpu);
|
||||
|
||||
cpu.getHardwareHost().attachHardware(radioHw, RadioReceiverHardware.DEFAULT_ADDRESS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.simon987.mar.npcplugin.event;
|
||||
|
||||
import net.simon987.mar.npcplugin.VaultExitPortal;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
|
||||
public class VaultCompleteEvent extends GameEvent {
|
||||
|
||||
private final VaultExitPortal portal;
|
||||
|
||||
public VaultCompleteEvent(GameObject object, VaultExitPortal portal) {
|
||||
|
||||
//TODO: Add completion time?
|
||||
setSource(object);
|
||||
this.portal = portal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameObject getSource() {
|
||||
return (GameObject) super.getSource();
|
||||
}
|
||||
|
||||
public VaultExitPortal getPortal() {
|
||||
return portal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.simon987.mar.npcplugin.event;
|
||||
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
|
||||
public class VaultCompleteListener implements GameEventListener {
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return VaultCompleteEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
VaultCompleteEvent vaultCompleteEvent = (VaultCompleteEvent) event;
|
||||
GameObject object = vaultCompleteEvent.getSource();
|
||||
if (object instanceof ControllableUnit) {
|
||||
LogManager.LOGGER.info(((ControllableUnit) object).getParent().getUsername() + " Completed vault " +
|
||||
object.getWorld().getDimension());
|
||||
|
||||
((ControllableUnit) object).getParent().getStats().addToStringSet("completedVaults",
|
||||
vaultCompleteEvent.getPortal().getWorld().getDimension());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package net.simon987.mar.npcplugin.event;
|
||||
|
||||
import net.simon987.mar.npcplugin.ElectricBox;
|
||||
import net.simon987.mar.npcplugin.VaultWorldUtils;
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.event.WorldUpdateEvent;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class VaultWorldUpdateListener implements GameEventListener {
|
||||
|
||||
/**
|
||||
* Map of worlds and their time to wait until next respawn event
|
||||
*/
|
||||
private final HashMap<World, Long> worldWaitMap = new HashMap<>(200);
|
||||
|
||||
/**
|
||||
* Lower bound of ElectricBox to be created on a respawn event
|
||||
*/
|
||||
private static int minElectricBoxCount;
|
||||
/**
|
||||
* Upper bound of ElectricBox to be created on a respawn event
|
||||
*/
|
||||
private static int maxElectricBoxCount;
|
||||
/**
|
||||
* Number of game ticks to wait after the threshold has been met
|
||||
*/
|
||||
private static int waitTime;
|
||||
/**
|
||||
* Threshold before starting the
|
||||
*/
|
||||
private static int electricBoxThreshold;
|
||||
|
||||
public VaultWorldUpdateListener(IServerConfiguration config) {
|
||||
|
||||
minElectricBoxCount = config.getInt("min_electric_box_respawn_count");
|
||||
maxElectricBoxCount = config.getInt("max_electric_box_respawn_count");
|
||||
waitTime = config.getInt("electric_box_respawnTime");
|
||||
electricBoxThreshold = config.getInt("min_electric_box_count");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return WorldUpdateEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
//TODO: Move this and the Biomass UpdateListener to a 'RespawnManager' kind of deal
|
||||
World world = ((WorldUpdateEvent) event).getWorld();
|
||||
|
||||
if (world.getDimension().startsWith("v")) {
|
||||
//If there is less than the respawn threshold,
|
||||
if (world.findObjects(ElectricBox.class).size() < electricBoxThreshold) {
|
||||
|
||||
//Set a timer for respawn_time ticks
|
||||
if (!worldWaitMap.containsKey(world) || worldWaitMap.get(world) == 0L) {
|
||||
worldWaitMap.put(world, GameServer.INSTANCE.getGameUniverse().getTime() + waitTime);
|
||||
} else {
|
||||
|
||||
long waitUntil = worldWaitMap.get(world);
|
||||
|
||||
if (GameServer.INSTANCE.getGameUniverse().getTime() >= waitUntil) {
|
||||
|
||||
//If the timer was set less than respawn_time ticks ago, respawn the blobs
|
||||
ArrayList<ElectricBox> newBoxes = VaultWorldUtils.generateElectricBoxes(world, minElectricBoxCount,
|
||||
maxElectricBoxCount);
|
||||
for (ElectricBox blob : newBoxes) {
|
||||
world.addObject(blob);
|
||||
world.incUpdatable();
|
||||
}
|
||||
|
||||
//Set the 'waitUntil' time to 0 to indicate that we are not waiting
|
||||
worldWaitMap.replace(world, 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package net.simon987.mar.npcplugin.event;
|
||||
|
||||
import net.simon987.mar.npcplugin.NpcPlugin;
|
||||
import net.simon987.mar.npcplugin.Settlement;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.event.WorldGenerationEvent;
|
||||
import net.simon987.mar.server.game.world.World;
|
||||
import net.simon987.mar.server.game.world.WorldGenerationException;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class WorldCreationListener implements GameEventListener {
|
||||
|
||||
/**
|
||||
* Spawn rate. Higher = rarer: A factory will be spawn about every FACTORY_SPAWN_RATE generated Worlds
|
||||
*/
|
||||
private static int FACTORY_SPAWN_RATE = 0;
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
public WorldCreationListener(int factorySpawnRate) {
|
||||
FACTORY_SPAWN_RATE = factorySpawnRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getListenedEventType() {
|
||||
return WorldGenerationEvent.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(GameEvent event) {
|
||||
|
||||
if (random.nextInt(FACTORY_SPAWN_RATE) == 0) {
|
||||
|
||||
World world = (World) event.getSource();
|
||||
|
||||
try {
|
||||
Settlement settlement = new Settlement(world);
|
||||
NpcPlugin.settlementMap.put(world.getId(), settlement);
|
||||
} catch (WorldGenerationException e) {
|
||||
LogManager.LOGGER.fine(String.format("Exception during settlement generation: %s.",
|
||||
e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package net.simon987.mar.npcplugin.world;
|
||||
|
||||
import net.simon987.mar.server.game.world.Tile;
|
||||
|
||||
public class TileVaultFloor extends Tile {
|
||||
|
||||
public static final int ID = 4;
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.simon987.mar.npcplugin.world;
|
||||
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.world.Tile;
|
||||
|
||||
public class TileVaultWall extends Tile {
|
||||
|
||||
public static final int ID = 5;
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean walk(GameObject object) {
|
||||
return false; //always blocked
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.simon987.mar.radioactivecloud;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.Enterable;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.Radioactive;
|
||||
|
||||
public class RadioactiveCloud extends GameObject implements Radioactive, Enterable {
|
||||
private final static int CORRUPTION_BLOCK_SIZE =
|
||||
GameServer.INSTANCE.getConfig().getInt("radioactive_cloud_corruption_block_size");
|
||||
|
||||
/**
|
||||
* Called when an object attempts to walk directly into a Enterable object
|
||||
*
|
||||
* @param object The game object that attempted to enter
|
||||
* @return true if successful, false to block the object
|
||||
*/
|
||||
@Override
|
||||
public boolean enter(GameObject object) {
|
||||
if (object instanceof ControllableUnit) {
|
||||
((ControllableUnit) object).getCpu().getMemory().corrupt(CORRUPTION_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.simon987.mar.radioactivecloud;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.Enterable;
|
||||
import net.simon987.mar.server.game.objects.GameObject;
|
||||
import net.simon987.mar.server.game.objects.Radioactive;
|
||||
|
||||
public class RadioactiveObstacle extends GameObject implements Radioactive, Enterable {
|
||||
|
||||
private final static int corruptionBlockSize = GameServer.INSTANCE.getConfig().getInt("radioactive_obstacle_corruption_block_size");
|
||||
private final static int MAP_INFO = 0x0A01; //10
|
||||
|
||||
@Override
|
||||
public char getMapInfo() {
|
||||
return MAP_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enter(GameObject object) {
|
||||
if (object instanceof ControllableUnit) {
|
||||
((ControllableUnit) object).getCpu().getMemory().corrupt(corruptionBlockSize);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
431
src/main/java/net/simon987/mar/server/GameServer.java
Normal file
431
src/main/java/net/simon987/mar/server/GameServer.java
Normal file
@@ -0,0 +1,431 @@
|
||||
package net.simon987.mar.server;
|
||||
|
||||
import com.mongodb.MongoClientException;
|
||||
import com.mongodb.client.*;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import net.simon987.mar.biomass.BiomassBlob;
|
||||
import net.simon987.mar.biomass.ItemBiomass;
|
||||
import net.simon987.mar.biomass.event.ObjectDeathListener;
|
||||
import net.simon987.mar.biomass.event.WorldCreationListener;
|
||||
import net.simon987.mar.biomass.event.WorldUpdateListener;
|
||||
import net.simon987.mar.construction.ConstructionSite;
|
||||
import net.simon987.mar.construction.ItemBluePrint;
|
||||
import net.simon987.mar.construction.Obstacle;
|
||||
import net.simon987.mar.cubot.*;
|
||||
import net.simon987.mar.cubot.event.*;
|
||||
import net.simon987.mar.mischwplugin.Clock;
|
||||
import net.simon987.mar.mischwplugin.RandomNumberGenerator;
|
||||
import net.simon987.mar.npcplugin.*;
|
||||
import net.simon987.mar.npcplugin.event.VaultCompleteListener;
|
||||
import net.simon987.mar.npcplugin.event.VaultWorldUpdateListener;
|
||||
import net.simon987.mar.npcplugin.world.TileVaultFloor;
|
||||
import net.simon987.mar.npcplugin.world.TileVaultWall;
|
||||
import net.simon987.mar.server.crypto.CryptoProvider;
|
||||
import net.simon987.mar.server.crypto.SecretKeyGenerator;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.event.GameEventDispatcher;
|
||||
import net.simon987.mar.server.event.GameEventListener;
|
||||
import net.simon987.mar.server.event.TickEvent;
|
||||
import net.simon987.mar.server.game.GameUniverse;
|
||||
import net.simon987.mar.server.game.debug.*;
|
||||
import net.simon987.mar.server.game.item.ItemCopper;
|
||||
import net.simon987.mar.server.game.item.ItemIron;
|
||||
import net.simon987.mar.server.game.objects.GameRegistry;
|
||||
import net.simon987.mar.server.game.world.*;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import net.simon987.mar.server.user.User;
|
||||
import net.simon987.mar.server.user.UserManager;
|
||||
import net.simon987.mar.server.user.UserStatsHelper;
|
||||
import net.simon987.mar.server.websocket.SocketServer;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GameServer implements Runnable {
|
||||
|
||||
public final static GameServer INSTANCE = new GameServer();
|
||||
|
||||
private final GameUniverse gameUniverse;
|
||||
private final GameEventDispatcher eventDispatcher;
|
||||
|
||||
private final IServerConfiguration config;
|
||||
|
||||
private SocketServer socketServer;
|
||||
|
||||
private final int maxExecutionTime;
|
||||
|
||||
private final DayNightCycle dayNightCycle;
|
||||
|
||||
private final CryptoProvider cryptoProvider;
|
||||
|
||||
private final MongoClient mongo;
|
||||
|
||||
private final UserManager userManager;
|
||||
|
||||
private final UserStatsHelper userStatsHelper;
|
||||
|
||||
private final GameRegistry gameRegistry;
|
||||
|
||||
private String secretKey;
|
||||
|
||||
public GameServer() {
|
||||
this.config = new ServerConfiguration("config.properties");
|
||||
|
||||
String connString = String.format("mongodb://%s:%d",
|
||||
config.getString("mongo_address"), config.getInt("mongo_port"));
|
||||
mongo = MongoClients.create(connString);
|
||||
MongoDatabase db = mongo.getDatabase(config.getString("mongo_dbname"));
|
||||
|
||||
MongoCollection<Document> userCollection = db.getCollection("user");
|
||||
|
||||
userManager = new UserManager(userCollection);
|
||||
userStatsHelper = new UserStatsHelper(userCollection);
|
||||
|
||||
gameUniverse = new GameUniverse(config);
|
||||
gameUniverse.setMongo(mongo);
|
||||
gameRegistry = new GameRegistry();
|
||||
|
||||
maxExecutionTime = config.getInt("user_timeout");
|
||||
|
||||
cryptoProvider = new CryptoProvider();
|
||||
|
||||
dayNightCycle = new DayNightCycle();
|
||||
|
||||
SecretKeyGenerator keyGenerator = new SecretKeyGenerator();
|
||||
secretKey = config.getString("secret_key");
|
||||
if (secretKey == null) {
|
||||
secretKey = keyGenerator.generate();
|
||||
config.setString("secret_key", secretKey);
|
||||
}
|
||||
|
||||
eventDispatcher = new GameEventDispatcher();
|
||||
|
||||
registerEventListeners();
|
||||
registerGameObjects();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void registerEventListeners() {
|
||||
|
||||
List<GameEventListener> listeners = eventDispatcher.getListeners();
|
||||
|
||||
listeners.add(dayNightCycle);
|
||||
|
||||
//Debug command Listeners
|
||||
listeners.add(new ComPortMsgCommandListener());
|
||||
listeners.add(new CreateWorldCommandListener());
|
||||
listeners.add(new KillAllCommandListener());
|
||||
listeners.add(new MoveObjCommandListener());
|
||||
listeners.add(new ObjInfoCommandListener());
|
||||
listeners.add(new SetTileAtCommandListener());
|
||||
listeners.add(new SpawnObjCommandListener());
|
||||
listeners.add(new TpObjectCommandListener());
|
||||
listeners.add(new UserInfoCommandListener());
|
||||
listeners.add(new HealObjCommandListener());
|
||||
listeners.add(new DamageObjCommandListener());
|
||||
listeners.add(new SetEnergyCommandListener());
|
||||
listeners.add(new SaveGameCommandListener());
|
||||
|
||||
|
||||
// Biomass
|
||||
listeners.add(new WorldCreationListener());
|
||||
listeners.add(new WorldUpdateListener(config));
|
||||
listeners.add(new ObjectDeathListener(config));
|
||||
|
||||
// Cubot
|
||||
listeners.add(new CpuInitialisationListener());
|
||||
listeners.add(new UserCreationListener());
|
||||
|
||||
listeners.add(new ChargeShieldCommandListener());
|
||||
listeners.add(new SetInventoryPosition());
|
||||
listeners.add(new PutItemCommandListener());
|
||||
listeners.add(new PopItemCommandListener());
|
||||
|
||||
listeners.add(new DeathListener());
|
||||
listeners.add(new WalkListener());
|
||||
|
||||
// NPC
|
||||
|
||||
listeners.add(new net.simon987.mar.npcplugin.event.WorldCreationListener(config.getInt("settlement_spawn_rate")));
|
||||
listeners.add(new net.simon987.mar.npcplugin.event.CpuInitialisationListener());
|
||||
listeners.add(new VaultWorldUpdateListener(config));
|
||||
listeners.add(new VaultCompleteListener());
|
||||
|
||||
}
|
||||
|
||||
private void registerGameObjects() {
|
||||
gameRegistry.registerItem(ItemCopper.ID, ItemCopper.class);
|
||||
gameRegistry.registerItem(ItemIron.ID, ItemIron.class);
|
||||
|
||||
gameRegistry.registerTile(TileVoid.ID, TileVoid.class);
|
||||
gameRegistry.registerTile(TilePlain.ID, TilePlain.class);
|
||||
gameRegistry.registerTile(TileWall.ID, TileWall.class);
|
||||
gameRegistry.registerTile(TileCopper.ID, TileCopper.class);
|
||||
gameRegistry.registerTile(TileIron.ID, TileIron.class);
|
||||
gameRegistry.registerTile(TileFluid.ID, TileFluid.class);
|
||||
|
||||
// Biomass
|
||||
gameRegistry.registerGameObject(BiomassBlob.class);
|
||||
gameRegistry.registerItem(ItemBiomass.ID, ItemBiomass.class);
|
||||
|
||||
// Construction
|
||||
gameRegistry.registerItem(ItemBluePrint.ID, ItemBluePrint.class);
|
||||
gameRegistry.registerGameObject(Obstacle.class);
|
||||
gameRegistry.registerGameObject(ConstructionSite.class);
|
||||
|
||||
// Cubot
|
||||
gameRegistry.registerGameObject(Cubot.class);
|
||||
gameRegistry.registerHardware(CubotLeg.class);
|
||||
gameRegistry.registerHardware(CubotLaser.class);
|
||||
gameRegistry.registerHardware(CubotLidar.class);
|
||||
gameRegistry.registerHardware(CubotDrill.class);
|
||||
gameRegistry.registerHardware(CubotInventory.class);
|
||||
gameRegistry.registerHardware(CubotKeyboard.class);
|
||||
gameRegistry.registerHardware(CubotHologram.class);
|
||||
gameRegistry.registerHardware(CubotBattery.class);
|
||||
gameRegistry.registerHardware(CubotFloppyDrive.class);
|
||||
gameRegistry.registerHardware(CubotComPort.class);
|
||||
gameRegistry.registerHardware(CubotShield.class);
|
||||
gameRegistry.registerHardware(CubotCore.class);
|
||||
|
||||
// Misc HW
|
||||
gameRegistry.registerHardware(RandomNumberGenerator.class);
|
||||
gameRegistry.registerHardware(Clock.class);
|
||||
|
||||
// NPC
|
||||
gameRegistry.registerGameObject(HarvesterNPC.class);
|
||||
gameRegistry.registerGameObject(Factory.class);
|
||||
gameRegistry.registerGameObject(RadioTower.class);
|
||||
gameRegistry.registerGameObject(VaultDoor.class);
|
||||
gameRegistry.registerGameObject(net.simon987.mar.npcplugin.Obstacle.class);
|
||||
gameRegistry.registerGameObject(ElectricBox.class);
|
||||
gameRegistry.registerGameObject(Portal.class);
|
||||
gameRegistry.registerGameObject(VaultExitPortal.class);
|
||||
gameRegistry.registerGameObject(HackedNPC.class);
|
||||
|
||||
gameRegistry.registerHardware(RadioReceiverHardware.class);
|
||||
gameRegistry.registerHardware(NpcBattery.class);
|
||||
gameRegistry.registerHardware(NpcInventory.class);
|
||||
|
||||
gameRegistry.registerTile(TileVaultFloor.ID, TileVaultFloor.class);
|
||||
gameRegistry.registerTile(TileVaultWall.ID, TileVaultWall.class);
|
||||
}
|
||||
|
||||
public GameUniverse getGameUniverse() {
|
||||
return gameUniverse;
|
||||
}
|
||||
|
||||
public GameEventDispatcher getEventDispatcher() {
|
||||
return eventDispatcher;
|
||||
}
|
||||
|
||||
public CryptoProvider getCryptoProvider() {
|
||||
return cryptoProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LogManager.LOGGER.info("(G) Started game loop");
|
||||
|
||||
long startTime; //Start time of the loop
|
||||
long uTime; //update time
|
||||
long waitTime; //time to wait
|
||||
|
||||
boolean running = true;
|
||||
|
||||
while (running) {
|
||||
|
||||
startTime = System.currentTimeMillis();
|
||||
|
||||
tick();
|
||||
|
||||
uTime = System.currentTimeMillis() - startTime;
|
||||
waitTime = config.getInt("tick_length") - uTime;
|
||||
|
||||
try {
|
||||
if (waitTime >= 0) {
|
||||
Thread.sleep(waitTime);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
gameUniverse.incrementTime();
|
||||
|
||||
//Dispatch tick event
|
||||
GameEvent event = new TickEvent(gameUniverse.getTime());
|
||||
eventDispatcher.dispatch(event); //Ignore cancellation
|
||||
|
||||
//Process user code
|
||||
for (User user : gameUniverse.getUsers()) {
|
||||
|
||||
if (user.getControlledUnit() != null && user.getControlledUnit().getCpu() != null) {
|
||||
try {
|
||||
|
||||
int timeout = Math.min(user.getControlledUnit().getEnergy(), maxExecutionTime);
|
||||
|
||||
user.getControlledUnit().getCpu().reset();
|
||||
int cost = user.getControlledUnit().getCpu().execute(timeout);
|
||||
user.getControlledUnit().spendEnergy(cost);
|
||||
user.addTime(cost);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogManager.LOGGER.severe("Error executing " + user.getUsername() + "'s code");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Process each worlds
|
||||
for (World world : gameUniverse.getWorlds()) {
|
||||
if (world.shouldUpdate()) {
|
||||
world.update();
|
||||
}
|
||||
}
|
||||
|
||||
//Save
|
||||
if (gameUniverse.getTime() % config.getInt("save_interval") == 0) {
|
||||
save();
|
||||
}
|
||||
|
||||
socketServer.tick();
|
||||
}
|
||||
|
||||
void load() {
|
||||
|
||||
LogManager.LOGGER.info("Loading all data from MongoDB");
|
||||
|
||||
MongoDatabase db = mongo.getDatabase(config.getString("mongo_dbname"));
|
||||
|
||||
MongoCollection<Document> worlds = db.getCollection("world");
|
||||
MongoCollection<Document> server = db.getCollection("server");
|
||||
|
||||
Document whereQuery = new Document();
|
||||
whereQuery.put("shouldUpdate", true);
|
||||
MongoCursor<Document> cursor = worlds.find(whereQuery).iterator();
|
||||
GameUniverse universe = GameServer.INSTANCE.getGameUniverse();
|
||||
while (cursor.hasNext()) {
|
||||
World w = World.deserialize(cursor.next());
|
||||
universe.addWorld(w);
|
||||
}
|
||||
|
||||
//Load users
|
||||
ArrayList<User> userList = userManager.getUsers();
|
||||
for (User user : userList) {
|
||||
universe.addUser(user);
|
||||
}
|
||||
|
||||
//Load server data
|
||||
cursor = server.find().iterator();
|
||||
if (cursor.hasNext()) {
|
||||
Document serverObj = cursor.next();
|
||||
gameUniverse.setTime((long) serverObj.get("time"));
|
||||
|
||||
// TODO: load gameUniverse.store data
|
||||
}
|
||||
|
||||
LogManager.LOGGER.info("Done loading! W:" + GameServer.INSTANCE.getGameUniverse().getWorldCount() +
|
||||
" | U:" + GameServer.INSTANCE.getGameUniverse().getUserCount());
|
||||
}
|
||||
|
||||
public void save() {
|
||||
|
||||
LogManager.LOGGER.info("Saving to MongoDB | W:" + gameUniverse.getWorldCount() + " | U:" + gameUniverse.getUserCount());
|
||||
|
||||
ClientSession session = null;
|
||||
try {
|
||||
try {
|
||||
session = mongo.startSession();
|
||||
session.startTransaction();
|
||||
} catch (MongoClientException e) {
|
||||
LogManager.LOGGER.fine("Could not create mongoDB session, will not use transaction feature. " +
|
||||
"(This message can be safely ignored)");
|
||||
}
|
||||
|
||||
MongoDatabase db = mongo.getDatabase(config.getString("mongo_dbname"));
|
||||
ReplaceOptions updateOptions = new ReplaceOptions();
|
||||
updateOptions.upsert(true);
|
||||
|
||||
int unloaded_worlds = 0;
|
||||
|
||||
MongoCollection<Document> worlds = db.getCollection("world");
|
||||
MongoCollection<Document> users = db.getCollection("user");
|
||||
MongoCollection<Document> server = db.getCollection("server");
|
||||
|
||||
int insertedWorlds = 0;
|
||||
GameUniverse universe = GameServer.INSTANCE.getGameUniverse();
|
||||
for (World w : universe.getWorlds()) {
|
||||
insertedWorlds++;
|
||||
worlds.replaceOne(new Document("_id", w.getId()), w.mongoSerialise(), updateOptions);
|
||||
|
||||
//If the world should unload, it is removed from the Universe after having been saved.
|
||||
if (w.shouldUnload()) {
|
||||
unloaded_worlds++;
|
||||
universe.removeWorld(w);
|
||||
}
|
||||
}
|
||||
|
||||
for (User u : GameServer.INSTANCE.getGameUniverse().getUsers()) {
|
||||
if (!u.isGuest()) {
|
||||
users.replaceOne(new Document("_id", u.getUsername()), u.mongoSerialise(), updateOptions);
|
||||
}
|
||||
}
|
||||
|
||||
Document serverObj = new Document();
|
||||
serverObj.put("time", gameUniverse.getTime());
|
||||
|
||||
//A constant id ensures only one entry is kept and updated, instead of a new entry created every save.
|
||||
server.replaceOne(new Document("_id", "serverinfo"), serverObj, updateOptions);
|
||||
if (session != null) {
|
||||
session.commitTransaction();
|
||||
}
|
||||
|
||||
LogManager.LOGGER.info("" + insertedWorlds + " worlds saved, " + unloaded_worlds + " unloaded");
|
||||
} catch (Exception e) {
|
||||
LogManager.LOGGER.severe("Problem happened during save function");
|
||||
e.printStackTrace();
|
||||
|
||||
if (session != null) {
|
||||
session.commitTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IServerConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setSocketServer(SocketServer socketServer) {
|
||||
this.socketServer = socketServer;
|
||||
}
|
||||
|
||||
public DayNightCycle getDayNightCycle() {
|
||||
return dayNightCycle;
|
||||
}
|
||||
|
||||
public UserManager getUserManager() {
|
||||
return userManager;
|
||||
}
|
||||
|
||||
public UserStatsHelper getUserStatsHelper() {
|
||||
return userStatsHelper;
|
||||
}
|
||||
|
||||
public GameRegistry getRegistry() {
|
||||
return gameRegistry;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
config.setString("secret_key", secretKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.simon987.mar.server;
|
||||
|
||||
public interface IServerConfiguration {
|
||||
|
||||
int getInt(String key);
|
||||
|
||||
String getString(String key);
|
||||
|
||||
void setInt(String key, int value);
|
||||
|
||||
void setString(String key, String value);
|
||||
}
|
||||
22
src/main/java/net/simon987/mar/server/Main.java
Normal file
22
src/main/java/net/simon987/mar/server/Main.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package net.simon987.mar.server;
|
||||
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import net.simon987.mar.server.web.WebServer;
|
||||
import spark.Spark;
|
||||
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
ServerConfiguration config = new ServerConfiguration("config.properties");
|
||||
LogManager.initialize(config);
|
||||
|
||||
GameServer.INSTANCE.load();
|
||||
|
||||
WebServer webServer = new WebServer(GameServer.INSTANCE.getConfig());
|
||||
|
||||
Spark.awaitInitialization();
|
||||
GameServer.INSTANCE.setSocketServer(webServer.getSocketServer());
|
||||
|
||||
(new Thread(GameServer.INSTANCE)).start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package net.simon987.mar.server;
|
||||
|
||||
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Wrapper for Java Properties class
|
||||
*/
|
||||
public class ServerConfiguration implements IServerConfiguration {
|
||||
|
||||
private Properties properties;
|
||||
private String fileName;
|
||||
|
||||
public ServerConfiguration() {
|
||||
this.properties = new Properties();
|
||||
}
|
||||
|
||||
public ServerConfiguration(String fileName) {
|
||||
|
||||
this.fileName = fileName;
|
||||
|
||||
try {
|
||||
properties = new Properties();
|
||||
InputStream is = new FileInputStream(this.fileName);
|
||||
properties.load(is);
|
||||
|
||||
} catch (IOException e) {
|
||||
LogManager.LOGGER.severe("Problem loading server configuration: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveConfig() {
|
||||
|
||||
try {
|
||||
OutputStream os = new FileOutputStream(this.fileName);
|
||||
properties.store(os, "");
|
||||
|
||||
} catch (IOException e) {
|
||||
LogManager.LOGGER.severe("Problem saving server configuration: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public int getInt(String key) {
|
||||
return Integer.valueOf((String) properties.get(key));
|
||||
|
||||
}
|
||||
|
||||
public String getString(String key) {
|
||||
|
||||
return (String) properties.get(key);
|
||||
}
|
||||
|
||||
public void setInt(String key, int value) {
|
||||
properties.setProperty(key, String.valueOf(value));
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
public void setString(String key, String value) {
|
||||
properties.setProperty(key, value);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
}
|
||||
604
src/main/java/net/simon987/mar/server/assembly/Assembler.java
Executable file
604
src/main/java/net/simon987/mar/server/assembly/Assembler.java
Executable file
@@ -0,0 +1,604 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.assembly.exception.*;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Top-level class for assembly operations.
|
||||
*/
|
||||
public class Assembler {
|
||||
|
||||
private static final String labelPattern = "^\\s*[a-zA-Z_]\\w*:";
|
||||
private static final Pattern commentPattern = Pattern.compile("\"[^\"]*\"|(;)");
|
||||
private final IServerConfiguration config;
|
||||
|
||||
private static int MEM_SIZE;
|
||||
private final InstructionSet instructionSet;
|
||||
private final RegisterSet registerSet;
|
||||
|
||||
public Assembler(InstructionSet instructionSet, RegisterSet registerSet, IServerConfiguration config) {
|
||||
this.instructionSet = instructionSet;
|
||||
this.registerSet = registerSet;
|
||||
this.config = config;
|
||||
|
||||
Assembler.MEM_SIZE = config.getInt("memory_size");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the comment part of a line
|
||||
*
|
||||
* @param line The line to trim
|
||||
* @return The line without its comment part
|
||||
*/
|
||||
private static String removeComment(String line) {
|
||||
|
||||
Matcher m = commentPattern.matcher(line);
|
||||
|
||||
while (m.find()) {
|
||||
try {
|
||||
return line.substring(0, m.start(1));
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the label part of a line
|
||||
*
|
||||
* @param line The line to trim
|
||||
* @return The line without its label part
|
||||
*/
|
||||
private static String removeLabel(String line) {
|
||||
|
||||
return line.replaceAll(labelPattern, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for and save the origin
|
||||
*
|
||||
* @param line Current line. Assuming that the comments and labels are removed
|
||||
* @param result Current line number
|
||||
*/
|
||||
private static void checkForORGInstruction(String line, AssemblyResult result, int currentLine)
|
||||
throws AssemblyException {
|
||||
line = removeComment(line);
|
||||
line = removeLabel(line);
|
||||
|
||||
//Split string
|
||||
String[] tokens = line.trim().split("\\s+");
|
||||
String mnemonic = tokens[0];
|
||||
|
||||
if (mnemonic.toUpperCase().equals("ORG")) {
|
||||
if (tokens.length > 1) {
|
||||
try {
|
||||
result.origin = (Integer.decode(tokens[1]));
|
||||
throw new PseudoInstructionException(currentLine);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidOperandException("Invalid operand \"" + tokens[1] + '"', currentLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for labels in a line and save it
|
||||
*
|
||||
* @param line Line to check
|
||||
* @param result Current assembly result
|
||||
* @param currentOffset Current offset in bytes
|
||||
*/
|
||||
private static void checkForLabel(String line, AssemblyResult result, char currentOffset) {
|
||||
|
||||
line = removeComment(line);
|
||||
|
||||
//Check for labels
|
||||
Pattern pattern = Pattern.compile(labelPattern);
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
|
||||
if (matcher.find()) {
|
||||
String label = matcher.group(0).substring(0, matcher.group(0).length() - 1).trim();
|
||||
|
||||
LogManager.LOGGER.fine("DEBUG: Label " + label + " @ " + (result.origin + currentOffset));
|
||||
result.labels.put(label, (char) (result.origin + currentOffset));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a line is empty
|
||||
*
|
||||
* @param line Line to check
|
||||
* @return true if a line only contains white space
|
||||
*/
|
||||
private static boolean isLineEmpty(String line) {
|
||||
return line.replaceAll("\\s+", "").isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the DW instruction (Define word). Handles DUP operator
|
||||
*
|
||||
* @param line Current line. assuming that comments and labels are removed
|
||||
* @param currentLine Current line number
|
||||
* @param labels Map of labels
|
||||
* @return Encoded instruction, null if the line is not a DW instruction
|
||||
*/
|
||||
private static byte[] parseDWInstruction(String line, HashMap<String, Character> labels, int currentLine)
|
||||
throws InvalidOperandException {
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream(bos);
|
||||
|
||||
//System.out.println(line);
|
||||
|
||||
if (line.length() >= 2 && line.substring(0, 2).toUpperCase().equals("DW")) {
|
||||
|
||||
try {
|
||||
|
||||
//Special thanks to https://stackoverflow.com/questions/1757065/
|
||||
String[] values = line.substring(2).split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
|
||||
|
||||
for (String value : values) {
|
||||
|
||||
value = value.trim();
|
||||
|
||||
String[] valueTokens = value.split("\\s+");
|
||||
|
||||
//Handle DUP operator
|
||||
if (valueTokens.length == 2 && valueTokens[1].toUpperCase().contains("DUP(")) {
|
||||
out.write(parseDUPOperator16(valueTokens, labels, currentLine));
|
||||
} else if (value.startsWith("\"") && value.endsWith("\"")) {
|
||||
//Handle string
|
||||
|
||||
//Unescape the string
|
||||
String string = value.substring(1, value.length() - 1);
|
||||
|
||||
try {
|
||||
string = StringEscapeUtils.unescapeJava(string);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidOperandException(
|
||||
"Invalid string operand \"" + string + "\": " + e.getMessage(),
|
||||
currentLine);
|
||||
}
|
||||
|
||||
out.write(string.getBytes(StandardCharsets.UTF_16BE));
|
||||
} else if (labels != null && labels.containsKey(value)) {
|
||||
//Handle label
|
||||
out.writeChar(labels.get(value));
|
||||
|
||||
} else {
|
||||
//Handle integer value
|
||||
try {
|
||||
out.writeChar(Integer.decode(value));
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
//Handle assumed label
|
||||
if (labels == null) {
|
||||
|
||||
// Write placeholder word
|
||||
out.writeChar(0);
|
||||
|
||||
} else {
|
||||
|
||||
//Integer.decode failed, try binary
|
||||
if (value.startsWith("0b")) {
|
||||
try {
|
||||
out.writeChar(Integer.parseInt(value.substring(2), 2));
|
||||
} catch (NumberFormatException e2) {
|
||||
throw new InvalidOperandException("Invalid operand \"" + value + '"', currentLine);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidOperandException("Invalid operand \"" + value + '"', currentLine);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return bos.toByteArray();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the DW instruction (Define word). Handles DUP operator
|
||||
*
|
||||
* @param line Current line. assuming that comments and labels are removed
|
||||
* @param currentLine Current line number
|
||||
* @return Encoded instruction, null if the line is not a DW instruction
|
||||
*/
|
||||
private static byte[] parseDWInstruction(String line, int currentLine) throws AssemblyException {
|
||||
return parseDWInstruction(line, null, currentLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the dup operator
|
||||
*
|
||||
* @param valueTokens Value tokens e.g. {"8", "DUP(12)"}
|
||||
* @param labels Map of labels
|
||||
* @return The encoded instruction
|
||||
*/
|
||||
private static byte[] parseDUPOperator16(String[] valueTokens, HashMap<String, Character> labels, int currentLine)
|
||||
throws InvalidOperandException {
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
|
||||
int factor = Integer.decode(valueTokens[0]);
|
||||
|
||||
if (factor > MEM_SIZE) {
|
||||
throw new InvalidOperandException("Factor '"+factor+"' exceeds total memory size", currentLine);
|
||||
}
|
||||
|
||||
String value = valueTokens[1].substring(4, valueTokens[1].lastIndexOf(')'));
|
||||
|
||||
//Handle label
|
||||
if (labels != null && labels.containsKey(value)) {
|
||||
//Label value is casted to byte
|
||||
for (int i = 0; i < factor; i++) {
|
||||
char s = labels.get(value);
|
||||
|
||||
out.write(Util.getHigherByte(s));
|
||||
out.write(Util.getLowerByte(s));
|
||||
}
|
||||
|
||||
} else {
|
||||
//Handle integer value
|
||||
char s = (char) (int) Integer.decode(value);
|
||||
|
||||
for (int i = 0; i < factor; i++) {
|
||||
out.write(Util.getHigherByte(s));
|
||||
out.write(Util.getLowerByte(s));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidOperandException("Usage: <factor> DUP(<value>)", currentLine);
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for and handle section declarations (.text and .data)
|
||||
*
|
||||
* @param line Current line
|
||||
*/
|
||||
private static void checkForSectionDeclaration(String line, AssemblyResult result,
|
||||
int currentLine, int currentOffset) throws AssemblyException {
|
||||
|
||||
String[] tokens = line.split("\\s+");
|
||||
|
||||
if (tokens[0].toUpperCase().equals(".TEXT")) {
|
||||
|
||||
result.defineSection(Section.TEXT, currentLine, currentOffset);
|
||||
throw new PseudoInstructionException(currentLine);
|
||||
|
||||
} else if (tokens[0].toUpperCase().equals(".DATA")) {
|
||||
|
||||
LogManager.LOGGER.fine("DEBUG: .data @" + currentLine);
|
||||
|
||||
result.defineSection(Section.DATA, currentLine, currentOffset);
|
||||
throw new PseudoInstructionException(currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for and handle the EQU instruction
|
||||
*
|
||||
* @param line Current line. The method is assuming that comments and labels are removed
|
||||
* @param labels Map of labels. Constants will be added as labels
|
||||
* @param currentLine Current line number
|
||||
*/
|
||||
private static void checkForEQUInstruction(String line, HashMap<String, Character> labels, int currentLine)
|
||||
throws AssemblyException {
|
||||
/* the EQU pseudo instruction is equivalent to the #define compiler directive in C/C++
|
||||
* usage: constant_name EQU <immediate_value>
|
||||
* A constant treated the same way as a label.
|
||||
*/
|
||||
line = line.trim();
|
||||
String[] tokens = line.split("\\s+");
|
||||
|
||||
|
||||
if (line.toUpperCase().matches(".*\\bEQU\\b.*")) {
|
||||
if (tokens[1].toUpperCase().equals("EQU") && tokens.length == 3) {
|
||||
try {
|
||||
//Save value as a label
|
||||
labels.put(tokens[0], (char) (int) Integer.decode(tokens[2]));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidOperandException("Usage: constant_name EQU immediate_value", currentLine);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidOperandException("Usage: constant_name EQU immediate_value", currentLine);
|
||||
}
|
||||
|
||||
throw new PseudoInstructionException(currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a text and assembles it. The assembler splits the text in
|
||||
* lines and parses them one by one. It does 3 passes, the first one
|
||||
* gets the origin of the code, the second one gets the label offsets
|
||||
* and the third pass encodes the instructions.
|
||||
*
|
||||
* @param text text to assemble
|
||||
* @return the result of the assembly. Includes the assembled code and
|
||||
* the errors, if any.
|
||||
*/
|
||||
public AssemblyResult parse(String text) {
|
||||
|
||||
int currentLine;
|
||||
|
||||
//Split in lines
|
||||
AssemblyResult result = new AssemblyResult(config);
|
||||
String[] lines = text.split("\n");
|
||||
|
||||
LogManager.LOGGER.info("Assembly job started: " + lines.length + " lines to parse.");
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
//Pass 1: Get code origin
|
||||
getCodeOrigin(lines, result);
|
||||
//Pass 2: Save label names and location
|
||||
saveLabelNamesAndLocation(lines, result);
|
||||
//Pass 3: encode instructions
|
||||
encodeInstructions(lines, result, out);
|
||||
|
||||
|
||||
//If the code contains OffsetOverFlowException(s), don't bother writing the assembled bytes to memory
|
||||
boolean writeToMemory = true;
|
||||
for (Exception e : result.exceptions) {
|
||||
if (e instanceof OffsetOverflowException) {
|
||||
writeToMemory = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (writeToMemory) {
|
||||
result.bytes = out.toByteArray();
|
||||
} else {
|
||||
result.bytes = new byte[0];
|
||||
LogManager.LOGGER.fine("Skipping writing assembled bytes to memory. (OffsetOverflowException)");
|
||||
}
|
||||
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
LogManager.LOGGER.info("Assembled " + result.bytes.length + " bytes (" + result.exceptions.size() + " errors)");
|
||||
for (AssemblyException e : result.exceptions) {
|
||||
LogManager.LOGGER.severe(e.getMessage() + '@' + e.getLine());
|
||||
}
|
||||
LogManager.LOGGER.info('\n' + Util.toHex(result.bytes));
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private void getCodeOrigin(String[] lines, AssemblyResult result) {
|
||||
for (int currentLine = 0; currentLine < lines.length; currentLine++) {
|
||||
try {
|
||||
checkForORGInstruction(lines[currentLine], result, currentLine);
|
||||
|
||||
} catch (PseudoInstructionException e) {
|
||||
break; //Origin is set, skip checking the rest
|
||||
|
||||
} catch (AssemblyException e) {
|
||||
//Ignore error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLabelNamesAndLocation(String[] lines, AssemblyResult result) {
|
||||
int currentOffset = 0;
|
||||
for (int currentLine = 0; currentLine < lines.length; currentLine++) {
|
||||
try {
|
||||
checkForLabel(lines[currentLine], result, (char)currentOffset);
|
||||
|
||||
//Increment offset
|
||||
currentOffset += parseInstruction(lines[currentLine], currentLine, instructionSet).length / 2;
|
||||
|
||||
if (currentOffset >= MEM_SIZE) {
|
||||
throw new OffsetOverflowException(currentOffset, MEM_SIZE, currentLine);
|
||||
}
|
||||
} catch (FatalAssemblyException e) {
|
||||
//Don't bother parsing the rest of the code, since it will not be assembled anyway
|
||||
break;
|
||||
} catch (AssemblyException e1) {
|
||||
//Ignore error on pass 2
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeInstructions(String[] lines, AssemblyResult result, ByteArrayOutputStream out) {
|
||||
int currentOffset = 0;
|
||||
for (int currentLine = 0; currentLine < lines.length; currentLine++) {
|
||||
|
||||
String line = lines[currentLine];
|
||||
|
||||
try {
|
||||
|
||||
line = removeComment(line);
|
||||
line = removeLabel(line);
|
||||
|
||||
if (isLineEmpty(line)) {
|
||||
throw new EmptyLineException(currentLine);
|
||||
}
|
||||
|
||||
//Check for pseudo instructions
|
||||
checkForSectionDeclaration(line, result, currentLine, currentOffset);
|
||||
checkForEQUInstruction(line, result.labels, currentLine);
|
||||
checkForORGInstruction(line, result, currentLine);
|
||||
|
||||
//Encode instruction
|
||||
byte[] bytes = parseInstruction(line, currentLine, result.labels, instructionSet);
|
||||
currentOffset += bytes.length / 2;
|
||||
|
||||
if (currentOffset >= MEM_SIZE) {
|
||||
throw new OffsetOverflowException(currentOffset, MEM_SIZE, currentLine);
|
||||
}
|
||||
|
||||
out.write(bytes);
|
||||
|
||||
} catch (EmptyLineException | PseudoInstructionException e) {
|
||||
//Ignore empty lines and pseudo-instructions
|
||||
} catch (FatalAssemblyException asmE) {
|
||||
// Save error, but abort assembly at this line
|
||||
result.exceptions.add(asmE);
|
||||
break;
|
||||
} catch (AssemblyException asmE) {
|
||||
//Save errors on pass3
|
||||
result.exceptions.add(asmE);
|
||||
} catch (IOException ioE) {
|
||||
ioE.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an instruction and encode it
|
||||
*
|
||||
* @param line Line to parse
|
||||
* @param currentLine Current line
|
||||
* @return The encoded instruction
|
||||
*/
|
||||
private byte[] parseInstruction(String line, int currentLine, InstructionSet instructionSet) throws AssemblyException {
|
||||
return parseInstruction(line, currentLine, null, instructionSet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an instruction and encode it
|
||||
*
|
||||
* @param line Line to parse
|
||||
* @param currentLine Current line
|
||||
* @param labels List of labels
|
||||
* @return The encoded instruction
|
||||
*/
|
||||
private byte[] parseInstruction(String line, int currentLine, HashMap<String, Character> labels,
|
||||
InstructionSet instructionSet)
|
||||
throws AssemblyException {
|
||||
return parseInstruction(line, currentLine, labels, instructionSet, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an instruction and encode it
|
||||
*
|
||||
* @param line Line to parse
|
||||
* @param currentLine Current line
|
||||
* @param labels List of labels
|
||||
* @param assumeLabels Assume that unknown operands are labels
|
||||
* @return The encoded instruction
|
||||
*/
|
||||
private byte[] parseInstruction(String line, int currentLine, HashMap<String, Character> labels,
|
||||
InstructionSet instructionSet, boolean assumeLabels)
|
||||
throws AssemblyException {
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
line = removeComment(line);
|
||||
line = removeLabel(line);
|
||||
line = line.trim();
|
||||
|
||||
if (isLineEmpty(line)) {
|
||||
throw new EmptyLineException(currentLine);
|
||||
}
|
||||
|
||||
//Split string
|
||||
String[] tokens = line.trim().split("\\s+");
|
||||
String mnemonic = tokens[0];
|
||||
|
||||
//Check for DW instruction
|
||||
try {
|
||||
if (assumeLabels) {
|
||||
byte[] bytes = parseDWInstruction(line, currentLine);
|
||||
if (bytes != null) {
|
||||
out.write(bytes);
|
||||
return out.toByteArray();
|
||||
}
|
||||
} else {
|
||||
byte[] bytes = parseDWInstruction(line, labels, currentLine);
|
||||
if (bytes != null) {
|
||||
out.write(bytes);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (instructionSet.get(mnemonic) == null) {
|
||||
throw new InvalidMnemonicException(mnemonic, currentLine);
|
||||
}
|
||||
|
||||
//Check operands and encode instruction
|
||||
final int beginIndex = line.indexOf(mnemonic) + mnemonic.length();
|
||||
if (line.contains(",")) {
|
||||
//2 operands
|
||||
String strO1 = line.substring(beginIndex, line.indexOf(','));
|
||||
String strO2 = line.substring(line.indexOf(','));
|
||||
|
||||
Operand o1, o2;
|
||||
|
||||
if (assumeLabels) {
|
||||
o1 = new Operand(strO1, registerSet, currentLine);
|
||||
o2 = new Operand(strO2, registerSet, currentLine);
|
||||
} else {
|
||||
o1 = new Operand(strO1, labels, registerSet, currentLine);
|
||||
o2 = new Operand(strO2, labels, registerSet, currentLine);
|
||||
}
|
||||
|
||||
//Encode instruction
|
||||
//Get instruction by name
|
||||
instructionSet.get(mnemonic).encode(out, o1, o2, currentLine);
|
||||
|
||||
} else if (tokens.length > 1) {
|
||||
//1 operand
|
||||
|
||||
String strO1 = line.substring(beginIndex);
|
||||
|
||||
Operand o1;
|
||||
if (assumeLabels) {
|
||||
o1 = new Operand(strO1, registerSet, currentLine);
|
||||
} else {
|
||||
o1 = new Operand(strO1, labels, registerSet, currentLine);
|
||||
}
|
||||
|
||||
//Encode instruction
|
||||
//Get instruction by name
|
||||
instructionSet.get(mnemonic).encode(out, o1, currentLine);
|
||||
} else {
|
||||
//No operand
|
||||
|
||||
//Encode instruction
|
||||
//Get instruction by name
|
||||
instructionSet.get(mnemonic).encode(out, currentLine);
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
124
src/main/java/net/simon987/mar/server/assembly/AssemblyResult.java
Executable file
124
src/main/java/net/simon987/mar/server/assembly/AssemblyResult.java
Executable file
@@ -0,0 +1,124 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.assembly.exception.AssemblyException;
|
||||
import net.simon987.mar.server.assembly.exception.DuplicateSectionException;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Result of an assembly attempt
|
||||
*/
|
||||
public class AssemblyResult {
|
||||
|
||||
/**
|
||||
* The origin of the program, default is 0x200
|
||||
*/
|
||||
public int origin;
|
||||
/**
|
||||
* A list of labels
|
||||
*/
|
||||
HashMap<String, Character> labels = new HashMap<>(20);
|
||||
/**
|
||||
* List of exceptions encountered during the assembly attempt,
|
||||
* they will be displayed in the editor
|
||||
*/
|
||||
public ArrayList<AssemblyException> exceptions = new ArrayList<>(50);
|
||||
/**
|
||||
* Offset of the code segment
|
||||
*/
|
||||
private int codeSectionOffset;
|
||||
/**
|
||||
* Line of the code segment definition
|
||||
*/
|
||||
private int codeSectionLine;
|
||||
|
||||
/**
|
||||
* The encoded user code (will be incomplete or invalid if the
|
||||
* assembler encountered an error during assembly)
|
||||
*/
|
||||
public byte[] bytes;
|
||||
/**
|
||||
* Offset of the data segment
|
||||
*/
|
||||
private int dataSectionOffset;
|
||||
/**
|
||||
* Line of the data segment definition
|
||||
*/
|
||||
private int dataSectionLine;
|
||||
/**
|
||||
* Whether or not the code segment is set
|
||||
*/
|
||||
private boolean codeSectionSet = false;
|
||||
/**
|
||||
* Whether or not the data segment is set
|
||||
*/
|
||||
private boolean dataSectionSet = false;
|
||||
|
||||
AssemblyResult(IServerConfiguration config) {
|
||||
origin = config.getInt("org_offset");
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a section.
|
||||
*
|
||||
* @param section Section to define
|
||||
* @param currentOffset Current offset, in bytes of the section
|
||||
* @param currentLine Line number of the section declaration
|
||||
* @throws DuplicateSectionException when a section is defined twice
|
||||
*/
|
||||
void defineSection(Section section, int currentLine, int currentOffset) throws DuplicateSectionException {
|
||||
|
||||
if (section == Section.TEXT) {
|
||||
//Code section
|
||||
|
||||
if (!codeSectionSet) {
|
||||
codeSectionOffset = origin + currentOffset;
|
||||
codeSectionLine = currentLine;
|
||||
|
||||
LogManager.LOGGER.fine("DEBUG: .text offset @" + codeSectionOffset);
|
||||
|
||||
|
||||
codeSectionSet = true;
|
||||
} else {
|
||||
throw new DuplicateSectionException(currentLine);
|
||||
}
|
||||
|
||||
} else {
|
||||
//Data section
|
||||
if (!dataSectionSet) {
|
||||
dataSectionOffset = origin + currentOffset;
|
||||
dataSectionLine = currentLine;
|
||||
|
||||
LogManager.LOGGER.fine("DEBUG: .data offset @" + dataSectionOffset);
|
||||
|
||||
dataSectionSet = true;
|
||||
} else {
|
||||
throw new DuplicateSectionException(currentLine);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public char[] getWords() {
|
||||
|
||||
char[] assembledCode = new char[bytes.length / 2];
|
||||
ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asCharBuffer().get(assembledCode);
|
||||
|
||||
return assembledCode;
|
||||
}
|
||||
|
||||
public int getCodeSectionOffset() {
|
||||
if (codeSectionSet) {
|
||||
return codeSectionOffset;
|
||||
} else {
|
||||
return origin;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
469
src/main/java/net/simon987/mar/server/assembly/CPU.java
Executable file
469
src/main/java/net/simon987/mar/server/assembly/CPU.java
Executable file
@@ -0,0 +1,469 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.IServerConfiguration;
|
||||
import net.simon987.mar.server.assembly.exception.CancelledException;
|
||||
import net.simon987.mar.server.assembly.instruction.*;
|
||||
import net.simon987.mar.server.event.CpuInitialisationEvent;
|
||||
import net.simon987.mar.server.event.GameEvent;
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.game.objects.HardwareHost;
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.bson.Document;
|
||||
|
||||
/**
|
||||
* CPU: Central Processing Unit. A CPU is capable of reading bytes from
|
||||
* a Memory object and execute them. A CPU object holds registers objects and
|
||||
* a Memory object.
|
||||
*/
|
||||
public class CPU implements MongoSerializable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private final Status status;
|
||||
|
||||
/**
|
||||
* Memory associated with the CPU, 64kb max
|
||||
*/
|
||||
private Memory memory;
|
||||
|
||||
/**
|
||||
* set of instructions of this CPU
|
||||
*/
|
||||
private final InstructionSet instructionSet;
|
||||
|
||||
/**
|
||||
* set of registers of this CPU
|
||||
*/
|
||||
private RegisterSet registerSet;
|
||||
|
||||
/**
|
||||
* Offset of the code section. The code starts to get
|
||||
* executed at this address each tick. Defaults to org_offset@config.properties
|
||||
*/
|
||||
private int codeSectionOffset;
|
||||
|
||||
/**
|
||||
* Instruction pointer, always points to the next instruction
|
||||
*/
|
||||
private int ip;
|
||||
|
||||
/**
|
||||
* Hardware is connected to the hardwareHost
|
||||
*/
|
||||
private HardwareHost hardwareHost;
|
||||
|
||||
private int registerSetSize;
|
||||
|
||||
private static final char EXECUTION_COST_ADDR = 0x0050;
|
||||
private static final char EXECUTED_INS_ADDR = 0x0051;
|
||||
|
||||
public CPU() {
|
||||
instructionSet = new DefaultInstructionSet();
|
||||
registerSet = new DefaultRegisterSet();
|
||||
codeSectionOffset = GameServer.INSTANCE.getConfig().getInt("org_offset");
|
||||
|
||||
instructionSet.add(new JmpInstruction(this));
|
||||
instructionSet.add(new JnzInstruction(this));
|
||||
instructionSet.add(new JzInstruction(this));
|
||||
instructionSet.add(new JgInstruction(this));
|
||||
instructionSet.add(new JgeInstruction(this));
|
||||
instructionSet.add(new JleInstruction(this));
|
||||
instructionSet.add(new JlInstruction(this));
|
||||
instructionSet.add(new PushInstruction(this));
|
||||
instructionSet.add(new PopInstruction(this));
|
||||
instructionSet.add(new CallInstruction(this));
|
||||
instructionSet.add(new RetInstruction(this));
|
||||
instructionSet.add(new MulInstruction(this));
|
||||
instructionSet.add(new DivInstruction(this));
|
||||
instructionSet.add(new JnsInstruction(this));
|
||||
instructionSet.add(new JsInstruction(this));
|
||||
instructionSet.add(new HwiInstruction(this));
|
||||
instructionSet.add(new HwqInstruction(this));
|
||||
instructionSet.add(new XchgInstruction(this));
|
||||
instructionSet.add(new JcInstruction(this));
|
||||
instructionSet.add(new JncInstruction(this));
|
||||
instructionSet.add(new JnoInstruction(this));
|
||||
instructionSet.add(new JoInstruction(this));
|
||||
instructionSet.add(new PushfInstruction(this));
|
||||
instructionSet.add(new PopfInstruction(this));
|
||||
instructionSet.add(new JnaInstruction(this));
|
||||
instructionSet.add(new JaInstruction(this));
|
||||
|
||||
status = new Status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CPU
|
||||
*/
|
||||
public CPU(IServerConfiguration config, ControllableUnit unit) throws CancelledException {
|
||||
instructionSet = new DefaultInstructionSet();
|
||||
registerSet = new DefaultRegisterSet();
|
||||
codeSectionOffset = config.getInt("org_offset");
|
||||
|
||||
instructionSet.add(new JmpInstruction(this));
|
||||
instructionSet.add(new JnzInstruction(this));
|
||||
instructionSet.add(new JzInstruction(this));
|
||||
instructionSet.add(new JgInstruction(this));
|
||||
instructionSet.add(new JgeInstruction(this));
|
||||
instructionSet.add(new JleInstruction(this));
|
||||
instructionSet.add(new JlInstruction(this));
|
||||
instructionSet.add(new PushInstruction(this));
|
||||
instructionSet.add(new PopInstruction(this));
|
||||
instructionSet.add(new CallInstruction(this));
|
||||
instructionSet.add(new RetInstruction(this));
|
||||
instructionSet.add(new MulInstruction(this));
|
||||
instructionSet.add(new DivInstruction(this));
|
||||
instructionSet.add(new JnsInstruction(this));
|
||||
instructionSet.add(new JsInstruction(this));
|
||||
instructionSet.add(new HwiInstruction(this));
|
||||
instructionSet.add(new HwqInstruction(this));
|
||||
instructionSet.add(new XchgInstruction(this));
|
||||
instructionSet.add(new JcInstruction(this));
|
||||
instructionSet.add(new JncInstruction(this));
|
||||
instructionSet.add(new JnoInstruction(this));
|
||||
instructionSet.add(new JoInstruction(this));
|
||||
instructionSet.add(new PushfInstruction(this));
|
||||
instructionSet.add(new PopfInstruction(this));
|
||||
instructionSet.add(new JnaInstruction(this));
|
||||
instructionSet.add(new JaInstruction(this));
|
||||
|
||||
status = new Status();
|
||||
memory = new Memory(config.getInt("memory_size"));
|
||||
|
||||
GameEvent event = new CpuInitialisationEvent(this, unit);
|
||||
GameServer.INSTANCE.getEventDispatcher().dispatch(event);
|
||||
if (event.isCancelled()) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
status.clear();
|
||||
ip = codeSectionOffset;
|
||||
}
|
||||
|
||||
public int execute(int timeout) {
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
int counter = 0;
|
||||
status.clear();
|
||||
|
||||
registerSetSize = registerSet.size();
|
||||
|
||||
// status.breakFlag = true;
|
||||
while (!status.isBreakFlag()) {
|
||||
counter++;
|
||||
|
||||
if (counter % 10000 == 0) {
|
||||
if (System.currentTimeMillis() > (startTime + timeout)) {
|
||||
LogManager.LOGGER.fine("CPU Timeout " + this + " after " + counter + "instructions (" + timeout + "ms): " + (double) counter / ((double) timeout / 1000) / 1000000 + "MHz");
|
||||
|
||||
//Write execution cost and instruction count to memory
|
||||
memory.set(EXECUTION_COST_ADDR, timeout);
|
||||
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
|
||||
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
|
||||
|
||||
return timeout;
|
||||
}
|
||||
}
|
||||
|
||||
//fetch instruction
|
||||
int machineCode = memory.get(ip);
|
||||
|
||||
/*
|
||||
* Contents of machineCode should look like this:
|
||||
* SSSS SDDD DDOO OOOO
|
||||
* Where S is source, D is destination and O is the opCode
|
||||
*/
|
||||
Instruction instruction = instructionSet.get(machineCode & 0x03F); // 0000 0000 00XX XXXX
|
||||
|
||||
int source = (machineCode >> 11) & 0x001F; // XXXX X000 0000 0000
|
||||
int destination = (machineCode >> 6) & 0x001F; // 0000 0XXX XX00 0000
|
||||
|
||||
executeInstruction(instruction, source, destination);
|
||||
// LogManager.LOGGER.info(instruction.getMnemonic());
|
||||
}
|
||||
int elapsed = (int) (System.currentTimeMillis() - startTime);
|
||||
|
||||
// LogManager.LOGGER.fine(counter + " instruction in " + elapsed + "ms : " + (double) counter / (elapsed / 1000) / 1000000 + "MHz");
|
||||
|
||||
|
||||
//Write execution cost and instruction count to memory
|
||||
memory.set(EXECUTION_COST_ADDR, elapsed);
|
||||
memory.set(EXECUTED_INS_ADDR, Util.getHigherWord(counter));
|
||||
memory.set(EXECUTED_INS_ADDR + 1, Util.getLowerWord(counter));
|
||||
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
public void executeInstruction(Instruction instruction, int source, int destination) {
|
||||
|
||||
//Execute the instruction
|
||||
if (source == 0) {
|
||||
//No operand (assuming that destination is also null)
|
||||
ip++;
|
||||
instruction.execute(status);
|
||||
} else if (source == Operand.IMMEDIATE_VALUE) {
|
||||
executeImmediateValue(instruction, source, destination);
|
||||
} else if (source == Operand.IMMEDIATE_VALUE_MEM) {
|
||||
executeImmediateValueMem(instruction, source, destination);
|
||||
} else if (source <= registerSetSize) {
|
||||
executeSourceIsRegister(instruction, source, destination);
|
||||
} else if (source <= registerSetSize * 2) {
|
||||
//Source is [reg]
|
||||
if (destination == 0) {
|
||||
//Single operand
|
||||
ip++;
|
||||
instruction.execute(memory, registerSet.get(source - registerSetSize), status);
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE) {
|
||||
//Destination is an immediate value
|
||||
//this shouldn't happen
|
||||
LogManager.LOGGER.severe("Trying to execute an instruction with an" +
|
||||
"immediate values as dst operand"); //todo remove debug info
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE_MEM) {
|
||||
//Destination is an memory immediate
|
||||
ip++;
|
||||
instruction.execute(memory, memory.get(ip++), memory, registerSet.get(source - registerSetSize), status);
|
||||
} else if (destination <= registerSetSize) {
|
||||
//Destination is a register
|
||||
ip++;
|
||||
instruction.execute(registerSet, destination, memory, registerSet.get(source - registerSetSize), status);
|
||||
} else if (destination <= registerSetSize * 2) {
|
||||
//Destination is [reg]
|
||||
ip++;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize), memory, registerSet.get(source - registerSetSize), status);
|
||||
} else {
|
||||
//Assuming that destination is [reg + x]
|
||||
ip += 2;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1),
|
||||
memory, registerSet.get(source - registerSetSize), status);
|
||||
}
|
||||
} else {
|
||||
//Assuming that source is [reg + X]
|
||||
|
||||
ip++;
|
||||
int sourceDisp = memory.get(ip);
|
||||
|
||||
if (destination == 0) {
|
||||
//Single operand
|
||||
ip += 1;
|
||||
instruction.execute(memory, registerSet.get(source - registerSetSize - registerSetSize) + memory.get(ip - 1), status);
|
||||
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE) {
|
||||
//Destination is an immediate value
|
||||
//this shouldn't happen
|
||||
LogManager.LOGGER.severe("Trying to execute an instruction with an" +
|
||||
"immediate values as dst operand"); //todo remove debug info
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE_MEM) {
|
||||
//Destination is memory immediate
|
||||
ip += 2;
|
||||
instruction.execute(memory, memory.get(ip - 1), memory,
|
||||
registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status);
|
||||
} else if (destination <= registerSetSize) {
|
||||
//Destination is a register
|
||||
ip++;
|
||||
instruction.execute(registerSet, destination, memory,
|
||||
registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status);
|
||||
} else if (destination <= registerSetSize * 2) {
|
||||
//Destination is [reg]
|
||||
ip++;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize), memory,
|
||||
registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status);
|
||||
} else {
|
||||
//Assuming that destination is [reg + x]
|
||||
ip += 2;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1),
|
||||
memory, registerSet.get(source - registerSetSize - registerSetSize) + sourceDisp, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeSourceIsRegister(Instruction instruction, int source, int destination) {
|
||||
//Source is a register
|
||||
if (destination == 0) {
|
||||
//Single operand
|
||||
ip++;
|
||||
instruction.execute(registerSet, source, status);
|
||||
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE) {
|
||||
//Destination is an immediate value
|
||||
//this shouldn't happen
|
||||
LogManager.LOGGER.severe("Trying to execute an instruction with an" +
|
||||
"immediate values as dst operand"); //todo remove debug info
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE_MEM) {
|
||||
//Destination is memory immediate
|
||||
ip += 2;
|
||||
instruction.execute(memory, memory.get(ip - 1), registerSet, source, status);
|
||||
} else if (destination <= registerSetSize) {
|
||||
//Destination is a register too
|
||||
ip++;
|
||||
instruction.execute(registerSet, destination, registerSet, source, status);
|
||||
} else if (destination <= registerSetSize * 2) {
|
||||
//Destination is [reg]
|
||||
ip++;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize), registerSet, source, status);
|
||||
} else {
|
||||
//Assuming that destination is [reg + x]
|
||||
ip += 2;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1),
|
||||
registerSet, source, status);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeImmediateValue(Instruction instruction, int source, int destination) {
|
||||
ip++;
|
||||
int sourceValue = memory.get(ip);
|
||||
|
||||
if (destination == 0) {
|
||||
//Single operand
|
||||
ip++;
|
||||
instruction.execute(sourceValue, status);
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE) {
|
||||
//Destination is an immediate value too
|
||||
//this shouldn't happen
|
||||
LogManager.LOGGER.severe("Trying to execute an instruction with 2" +
|
||||
"immediate values as operands"); //todo remove debug info
|
||||
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE_MEM) {
|
||||
//Destination is memory immediate
|
||||
ip += 2;
|
||||
instruction.execute(memory, memory.get(ip - 1), sourceValue, status);
|
||||
} else if (destination <= registerSetSize) {
|
||||
//Destination is a register
|
||||
ip++;
|
||||
instruction.execute(registerSet, destination, sourceValue, status);
|
||||
|
||||
} else if (destination <= registerSetSize * 2) {
|
||||
//Destination is [reg]
|
||||
ip++;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize), sourceValue, status);
|
||||
} else {
|
||||
//Assuming that destination is [reg + x]
|
||||
ip += 2;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1),
|
||||
sourceValue, status);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeImmediateValueMem(Instruction instruction, int source, int destination) {
|
||||
//Source is [x]
|
||||
ip++;
|
||||
int sourceValue = memory.get(memory.get(ip));
|
||||
|
||||
if (destination == 0) {
|
||||
//Single operand
|
||||
ip++;
|
||||
instruction.execute(memory, memory.get(ip - 1), status);
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE) {
|
||||
//Destination is an immediate value
|
||||
|
||||
//this shouldn't happen
|
||||
LogManager.LOGGER.severe("Trying to execute an instruction with an" +
|
||||
"immediate values as dst operand"); //todo remove debug info
|
||||
} else if (destination == Operand.IMMEDIATE_VALUE_MEM) {
|
||||
//Destination is memory immediate too
|
||||
ip += 2;
|
||||
instruction.execute(memory, memory.get(ip - 1), sourceValue, status);
|
||||
} else if (destination <= registerSetSize) {
|
||||
//Destination is a register
|
||||
ip++;
|
||||
instruction.execute(registerSet, destination, sourceValue, status);
|
||||
} else if (destination <= registerSetSize * 2) {
|
||||
//Destination is [reg]
|
||||
ip++;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize), sourceValue, status);
|
||||
} else {
|
||||
//Assuming that destination is [reg + x]
|
||||
ip += 2;
|
||||
instruction.execute(memory, registerSet.get(destination - registerSetSize - registerSetSize) + memory.get(ip - 1), sourceValue, status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document dbObject = new Document();
|
||||
|
||||
dbObject.put("memory", memory.mongoSerialise());
|
||||
|
||||
dbObject.put("registerSet", registerSet.mongoSerialise());
|
||||
dbObject.put("codeSegmentOffset", codeSectionOffset);
|
||||
|
||||
|
||||
return dbObject;
|
||||
|
||||
}
|
||||
|
||||
public static CPU deserialize(Document obj, ControllableUnit unit) throws CancelledException {
|
||||
|
||||
CPU cpu = new CPU(GameServer.INSTANCE.getConfig(), unit);
|
||||
|
||||
cpu.codeSectionOffset = obj.getInteger("codeSegmentOffset");
|
||||
|
||||
cpu.memory = new Memory((Document) obj.get("memory"));
|
||||
cpu.registerSet = RegisterSet.deserialize((Document) obj.get("registerSet"));
|
||||
|
||||
return cpu;
|
||||
|
||||
}
|
||||
|
||||
public InstructionSet getInstructionSet() {
|
||||
return instructionSet;
|
||||
}
|
||||
|
||||
public RegisterSet getRegisterSet() {
|
||||
return registerSet;
|
||||
}
|
||||
|
||||
public Memory getMemory() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
public void setMemory(Memory memory) {
|
||||
this.memory = memory;
|
||||
}
|
||||
|
||||
public void setRegisterSet(RegisterSet registerSet) {
|
||||
this.registerSet = registerSet;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public int getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(char ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public int getCodeSectionOffset() {
|
||||
return codeSectionOffset;
|
||||
}
|
||||
|
||||
public void setCodeSectionOffset(int codeSectionOffset) {
|
||||
this.codeSectionOffset = codeSectionOffset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String str = registerSet.toString();
|
||||
str += status.toString();
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public HardwareHost getHardwareHost() {
|
||||
return hardwareHost;
|
||||
}
|
||||
|
||||
public void setHardwareHost(HardwareHost hardwareHost) {
|
||||
this.hardwareHost = hardwareHost;
|
||||
}
|
||||
}
|
||||
140
src/main/java/net/simon987/mar/server/assembly/DefaultInstructionSet.java
Executable file
140
src/main/java/net/simon987/mar/server/assembly/DefaultInstructionSet.java
Executable file
@@ -0,0 +1,140 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
import net.simon987.mar.server.assembly.instruction.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default instruction set for the CPU
|
||||
*/
|
||||
public class DefaultInstructionSet implements InstructionSet {
|
||||
|
||||
/**
|
||||
* Map of instructions, stored in opcode : Instruction format
|
||||
*/
|
||||
private final Map<Integer, Instruction> instructionMap = new HashMap<>(32);
|
||||
|
||||
/**
|
||||
* Map of aliasses, stored in mnemonic : Instruction format
|
||||
*/
|
||||
private final Map<String, Instruction> aliasesMap = new HashMap<>(16);
|
||||
|
||||
private final Instruction defaultInstruction;
|
||||
|
||||
/**
|
||||
* Create an empty instruction set
|
||||
*/
|
||||
DefaultInstructionSet() {
|
||||
Instruction nop = new NopInstruction();
|
||||
defaultInstruction = nop;
|
||||
|
||||
add(nop);
|
||||
add(new BrkInstruction());
|
||||
add(new MovInstruction());
|
||||
add(new AddInstruction());
|
||||
add(new SubInstruction());
|
||||
add(new AndInstruction());
|
||||
add(new OrInstruction());
|
||||
add(new ShlInstruction());
|
||||
add(new ShrInstruction());
|
||||
add(new XorInstruction());
|
||||
add(new TestInstruction());
|
||||
add(new CmpInstruction());
|
||||
add(new NegInstruction());
|
||||
add(new NotInstruction());
|
||||
add(new RorInstruction());
|
||||
add(new RolInstruction());
|
||||
add(new RclInstruction());
|
||||
add(new RcrInstruction());
|
||||
add(new SarInstruction());
|
||||
add(new IncInstruction());
|
||||
add(new DecInstruction());
|
||||
|
||||
// 'abstract' instruction
|
||||
add(new SetccInstruction());
|
||||
|
||||
// aliases
|
||||
add(new SalInstruction());
|
||||
|
||||
// Setcc family
|
||||
add(new SetaeInstruction());
|
||||
add(new SetaInstruction());
|
||||
add(new SetbeInstruction());
|
||||
add(new SetbInstruction());
|
||||
add(new SetcInstruction());
|
||||
add(new SeteInstruction());
|
||||
add(new SetgeInstruction());
|
||||
add(new SetgInstruction());
|
||||
add(new SetleInstruction());
|
||||
add(new SetlInstruction());
|
||||
add(new SetnaeInstruction());
|
||||
add(new SetnaInstruction());
|
||||
add(new SetnbeInstruction());
|
||||
add(new SetnbInstruction());
|
||||
add(new SetncInstruction());
|
||||
add(new SetneInstruction());
|
||||
add(new SetngeInstruction());
|
||||
add(new SetngInstruction());
|
||||
add(new SetnleInstruction());
|
||||
add(new SetnlInstruction());
|
||||
add(new SetnoInstruction());
|
||||
add(new SetnsInstruction());
|
||||
add(new SetnzInstruction());
|
||||
add(new SetoInstruction());
|
||||
add(new SetsInstruction());
|
||||
add(new SetzInstruction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instruction from its opcode
|
||||
*
|
||||
* @param opcode opcode of the instruction
|
||||
* @return the instruction, default is not found
|
||||
*/
|
||||
@Override
|
||||
public Instruction get(int opcode) {
|
||||
|
||||
Instruction instruction = instructionMap.get(opcode);
|
||||
if (instruction != null) {
|
||||
return instruction;
|
||||
} else {
|
||||
// System.out.println("Invalid instruction " + opcode);
|
||||
//Todo: Notify user? Set error flag?
|
||||
return defaultInstruction;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get an instruction from its mnemonic
|
||||
*/
|
||||
@Override
|
||||
public Instruction get(String mnemonic) {
|
||||
for (Instruction ins : instructionMap.values()) {
|
||||
if (ins.getMnemonic().equalsIgnoreCase(mnemonic)) {
|
||||
return ins;
|
||||
}
|
||||
}
|
||||
|
||||
Instruction aliasedInstruction = aliasesMap.get(mnemonic.toLowerCase());
|
||||
return aliasedInstruction;
|
||||
}
|
||||
/**
|
||||
* Add a new instruction to the instructionSet
|
||||
*
|
||||
* @param opcode opcode of the instruction
|
||||
* @param instruction Instruction to add
|
||||
*/
|
||||
public void addInstruction(int opcode, Instruction instruction) {
|
||||
instructionMap.put(opcode, instruction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Instruction instruction) {
|
||||
Instruction aliasedInstruction = instructionMap.get(instruction.getOpCode());
|
||||
if (aliasedInstruction != null) {
|
||||
aliasesMap.put(instruction.getMnemonic(), instruction);
|
||||
} else {
|
||||
instructionMap.put(instruction.getOpCode(), instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/main/java/net/simon987/mar/server/assembly/DefaultRegisterSet.java
Executable file
22
src/main/java/net/simon987/mar/server/assembly/DefaultRegisterSet.java
Executable file
@@ -0,0 +1,22 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* RegisterSet with default values
|
||||
*/
|
||||
class DefaultRegisterSet extends RegisterSet {
|
||||
|
||||
|
||||
DefaultRegisterSet() {
|
||||
super();
|
||||
|
||||
addRegister(1, new Register("A"));
|
||||
addRegister(2, new Register("B"));
|
||||
addRegister(3, new Register("C"));
|
||||
addRegister(4, new Register("D"));
|
||||
addRegister(5, new Register("X"));
|
||||
addRegister(6, new Register("Y"));
|
||||
addRegister(7, new Register("SP"));
|
||||
addRegister(8, new Register("BP"));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
|
||||
import net.simon987.mar.server.game.objects.ControllableUnit;
|
||||
import net.simon987.mar.server.io.JSONSerializable;
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
|
||||
public abstract class HardwareModule implements MongoSerializable, JSONSerializable {
|
||||
|
||||
private CPU cpu;
|
||||
protected ControllableUnit unit;
|
||||
|
||||
public HardwareModule() {
|
||||
|
||||
}
|
||||
|
||||
public HardwareModule(Document document, ControllableUnit unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an HWI instruction
|
||||
*/
|
||||
public abstract void handleInterrupt(Status status);
|
||||
|
||||
protected CPU getCpu() {
|
||||
return cpu;
|
||||
}
|
||||
|
||||
public void setCpu(CPU cpu) {
|
||||
this.cpu = cpu;
|
||||
}
|
||||
|
||||
public abstract char getId();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
JSONObject hwJson = jsonSerialise();
|
||||
return String.format("{%s: {%s}}", getClass().getSimpleName(), hwJson == null ? "" : hwJson);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
|
||||
}
|
||||
|
||||
public void update() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject jsonSerialise() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject debugJsonSerialise() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
Document document = new Document();
|
||||
|
||||
document.put("type", getClass().getCanonicalName());
|
||||
return document;
|
||||
}
|
||||
}
|
||||
214
src/main/java/net/simon987/mar/server/assembly/Instruction.java
Executable file
214
src/main/java/net/simon987/mar/server/assembly/Instruction.java
Executable file
@@ -0,0 +1,214 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
import net.simon987.mar.server.assembly.exception.AssemblyException;
|
||||
import net.simon987.mar.server.assembly.exception.IllegalOperandException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* Represents a instruction type (e.g. MOV) that performs an action
|
||||
* base on 0-2 Targets
|
||||
*/
|
||||
public abstract class Instruction {
|
||||
|
||||
|
||||
/**
|
||||
* Symbolic name of the instruction
|
||||
*/
|
||||
private final String mnemonic;
|
||||
|
||||
/**
|
||||
* Opcode of the instruction (6-bit signed integer)
|
||||
*/
|
||||
private final int opCode;
|
||||
|
||||
/**
|
||||
* Create a new Instruction
|
||||
*
|
||||
* @param mnemonic Mnemonic of the instruction
|
||||
* @param opCode opCode of the instruction (6-bit signed integer)
|
||||
*/
|
||||
public Instruction(String mnemonic, int opCode) {
|
||||
this.mnemonic = mnemonic;
|
||||
this.opCode = opCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an instruction with 2 operands
|
||||
*
|
||||
* @param dst Destination operand
|
||||
* @param dstIndex Index of the destination operand
|
||||
* @param src Source operand
|
||||
* @param srcIndex Index of the source operand
|
||||
* @param status Status of the CPU before the execution
|
||||
* @return Status after the execution
|
||||
*/
|
||||
public Status execute(Target dst, int dstIndex, Target src, int srcIndex, Status status) {
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an instruction with 2 operands, the second one being an immediate operand
|
||||
*
|
||||
* @param dst Destination operand
|
||||
* @param dstIndex Index of the destination operand
|
||||
* @param src Source operand
|
||||
* @param status Status of the CPU before execution
|
||||
* @return Status after the execution
|
||||
*/
|
||||
public Status execute(Target dst, int dstIndex, int src, Status status) {
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an instruction with 1 operand
|
||||
*
|
||||
* @param src Source operand
|
||||
* @param srcIndex Index of the source operand
|
||||
* @param status Status of the CPU before execution
|
||||
* @return Status after execution
|
||||
*/
|
||||
public Status execute(Target src, int srcIndex, Status status) {
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an instruction with 1 operand that is an immediate value
|
||||
*
|
||||
* @param src Source operand
|
||||
* @param status Status of the CPU before execution
|
||||
* @return Status after execution
|
||||
*/
|
||||
public Status execute(int src, Status status) {
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an instruction that doesn't take any operand
|
||||
*/
|
||||
public Status execute(Status status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the operand combinaison is valid.
|
||||
*
|
||||
* @param o1 Destination operand
|
||||
* @param o2 Source operand
|
||||
* @return true if valid
|
||||
*/
|
||||
public boolean operandsValid(Operand o1, Operand o2) {
|
||||
return o1.getType() != OperandType.IMMEDIATE16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the operand is valid for this instruction
|
||||
*
|
||||
* @param o1 source operand
|
||||
* @return true if the specified operand can be used with this instruction
|
||||
*/
|
||||
public boolean operandValid(Operand o1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the instruction is valid without any
|
||||
* operands
|
||||
*/
|
||||
public boolean noOperandsValid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getMnemonic() {
|
||||
return mnemonic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the instruction. Writes the result in the outputStream
|
||||
*
|
||||
* @param out encoded bytes will be written here
|
||||
*/
|
||||
public void encode(ByteArrayOutputStream out, int currentLine) throws AssemblyException {
|
||||
|
||||
if (!noOperandsValid()) {
|
||||
throw new IllegalOperandException("This instruction must have operand(s)!", currentLine);
|
||||
}
|
||||
|
||||
MachineCode code = new MachineCode();
|
||||
code.writeOpcode(opCode);
|
||||
|
||||
for (byte b : code.bytes()) {
|
||||
out.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
public void encode(ByteArrayOutputStream out, Operand o1, Operand o2, int currentLine)
|
||||
throws AssemblyException {
|
||||
MachineCode code = new MachineCode();
|
||||
code.writeOpcode(opCode);
|
||||
|
||||
if (!operandsValid(o1, o2)) {
|
||||
throw new IllegalOperandException("Illegal operand combination : " + o1.getType()
|
||||
+ " and " + o2.getType(), currentLine);
|
||||
}
|
||||
|
||||
//Source operand
|
||||
if (o2.getType() == OperandType.REGISTER16 || o2.getType() == OperandType.MEMORY_REG16) {
|
||||
//operand can be stored in its 5-bit space
|
||||
code.writeSourceOperand(o2.getValue());
|
||||
} else {
|
||||
//operand needs to be stored in another word
|
||||
code.writeSourceOperand(o2.getValue());
|
||||
code.appendWord((char) o2.getData());
|
||||
}
|
||||
|
||||
//Destination operand
|
||||
if (o1.getType() == OperandType.REGISTER16 || o1.getType() == OperandType.MEMORY_REG16) {
|
||||
//operand can be stored in its 5-bit space
|
||||
code.writeDestinationOperand(o1.getValue());
|
||||
} else {
|
||||
//operand needs to be stored in another word
|
||||
code.writeDestinationOperand(o1.getValue());
|
||||
code.appendWord((char) o1.getData());
|
||||
}
|
||||
|
||||
for (byte b : code.bytes()) {
|
||||
out.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
public void encode(ByteArrayOutputStream out, Operand o1, int currentLine)
|
||||
throws AssemblyException {
|
||||
MachineCode code = new MachineCode();
|
||||
code.writeOpcode(opCode);
|
||||
|
||||
if (!operandValid(o1)) {
|
||||
throw new IllegalOperandException("Illegal operand combination: " + o1.getType() + " (none)", currentLine);
|
||||
}
|
||||
|
||||
//Source operand
|
||||
if (o1.getType() == OperandType.REGISTER16 || o1.getType() == OperandType.MEMORY_REG16) {
|
||||
//operand can be stored in its 5-bit space
|
||||
code.writeSourceOperand(o1.getValue());
|
||||
} else {
|
||||
//operand needs to be stored in another word
|
||||
code.writeSourceOperand(o1.getValue());
|
||||
code.appendWord((char) o1.getData());
|
||||
}
|
||||
|
||||
//Destination bits are left blank
|
||||
|
||||
for (byte b : code.bytes()) {
|
||||
out.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
public int getOpCode() {
|
||||
return opCode;
|
||||
}
|
||||
}
|
||||
33
src/main/java/net/simon987/mar/server/assembly/InstructionSet.java
Executable file
33
src/main/java/net/simon987/mar/server/assembly/InstructionSet.java
Executable file
@@ -0,0 +1,33 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* A set of instructions for a CPU.
|
||||
* <p>
|
||||
* Defines what a CPU can do
|
||||
*/
|
||||
public interface InstructionSet {
|
||||
|
||||
/**
|
||||
* Get an instruction by its opcode
|
||||
*
|
||||
* @param opcode opcode of the instruction
|
||||
* @return the instruction, null is not found
|
||||
*/
|
||||
Instruction get(int opcode);
|
||||
|
||||
/**
|
||||
* Get an instruction by its mnemonic
|
||||
*
|
||||
* @param mnemonic mnemonic of the instruction, not case sensitive
|
||||
* @return the instruction, if not found, the default instruction is returned
|
||||
*/
|
||||
Instruction get(String mnemonic);
|
||||
|
||||
/**
|
||||
* Add an instruction to the set
|
||||
*
|
||||
* @param instruction instruction to add
|
||||
*/
|
||||
void add(Instruction instruction);
|
||||
|
||||
}
|
||||
98
src/main/java/net/simon987/mar/server/assembly/MachineCode.java
Executable file
98
src/main/java/net/simon987/mar/server/assembly/MachineCode.java
Executable file
@@ -0,0 +1,98 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* Represents an encoded instruction. this class is used to easily
|
||||
* write to an 16bit value.
|
||||
*/
|
||||
public class MachineCode {
|
||||
|
||||
/**
|
||||
* Value of the initial 2-byte instruction
|
||||
*/
|
||||
private char value;
|
||||
|
||||
/**
|
||||
* Appended words after the instruction bytes. Used to store immediate values
|
||||
*/
|
||||
private final ArrayList<Character> additionalWords = new ArrayList<>(2);
|
||||
|
||||
/**
|
||||
* Write the opCode in the 6 least significant bit
|
||||
*
|
||||
* @param opCode signed 6-bit integer (value 0-63)
|
||||
*/
|
||||
public void writeOpcode(int opCode) {
|
||||
|
||||
if (opCode < 0 || opCode > 63) {
|
||||
LogManager.LOGGER.severe("Couldn't write the opCode for instruction :" + opCode);
|
||||
} else {
|
||||
|
||||
//OpCode is the 6 least significant bits
|
||||
value &= 0xFFC0; // 1111 1111 1100 0000 mask last 6 bits
|
||||
value |= opCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the source operand in the bits 6-11 (bit 0 being the least significant)
|
||||
*
|
||||
* @param src signed 5-bit integer (value 0-31)
|
||||
*/
|
||||
public void writeSourceOperand(int src) {
|
||||
|
||||
if (src < 0 || src > 31) {
|
||||
LogManager.LOGGER.severe("Couldn't write the scr operand for instruction :" + src);
|
||||
} else {
|
||||
|
||||
//Src is the 5 most significant bits
|
||||
value &= 0x07FF; //0000 0111 1111 1111
|
||||
src <<= 11; //XXXX X000 0000 0000
|
||||
value |= src;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the destination operand in the 5 most significant bits
|
||||
*
|
||||
* @param dst signed 5-bit integer (value 0-31)
|
||||
*/
|
||||
public void writeDestinationOperand(int dst) {
|
||||
if (dst < 0 || dst > 31) {
|
||||
LogManager.LOGGER.severe("Couldn't write the dst operand for instruction :" + dst);
|
||||
} else {
|
||||
|
||||
value &= 0xF83F; //1111 1000 0011 1111
|
||||
dst <<= 6; //0000 0XXX XX00 0000
|
||||
value |= dst;
|
||||
}
|
||||
}
|
||||
|
||||
public void appendWord(char word) {
|
||||
additionalWords.add(word);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bytes of the code
|
||||
*/
|
||||
public byte[] bytes() {
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
out.write(value >> 8);
|
||||
out.write(value);
|
||||
|
||||
for (Character s : additionalWords) {
|
||||
out.write(s >> 8);
|
||||
out.write(s);
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
189
src/main/java/net/simon987/mar/server/assembly/Memory.java
Executable file
189
src/main/java/net/simon987/mar/server/assembly/Memory.java
Executable file
@@ -0,0 +1,189 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
|
||||
import net.simon987.mar.server.GameServer;
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
|
||||
/**
|
||||
* Represents the available memory for a CPU in the game universe
|
||||
*/
|
||||
public class Memory implements Target, MongoSerializable {
|
||||
|
||||
|
||||
/**
|
||||
* Contents of the memory
|
||||
*/
|
||||
private char[] words;
|
||||
|
||||
/**
|
||||
* Create an empty Memory object
|
||||
*
|
||||
* @param size Size of the memory, in words
|
||||
*/
|
||||
public Memory(int size) {
|
||||
words = new char[size];
|
||||
}
|
||||
|
||||
public Memory(Document document) {
|
||||
|
||||
String zipBytesStr = (String) document.get("zipBytes");
|
||||
|
||||
if (zipBytesStr != null) {
|
||||
byte[] compressedBytes = Base64.getDecoder().decode((String) document.get("zipBytes"));
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Inflater decompressor = new Inflater(true);
|
||||
InflaterOutputStream inflaterOutputStream = new InflaterOutputStream(baos, decompressor);
|
||||
inflaterOutputStream.write(compressedBytes);
|
||||
inflaterOutputStream.close();
|
||||
|
||||
setBytes(baos.toByteArray());
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
LogManager.LOGGER.severe("Memory was manually deleted");
|
||||
words = new char[GameServer.INSTANCE.getConfig().getInt("memory_size")];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value at an address
|
||||
*
|
||||
* @param address Address of the value
|
||||
* @return 16-bit value at the specified address
|
||||
*/
|
||||
@Override
|
||||
public int get(int address) {
|
||||
address = (char) address;
|
||||
|
||||
if (address >= words.length) {
|
||||
LogManager.LOGGER.info("DEBUG: Trying to get memory out of bounds " + address);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return words[address];
|
||||
}
|
||||
|
||||
/**
|
||||
* Write x words from an array at an offset
|
||||
*/
|
||||
public boolean write(int offset, char[] src, int srcOffset, int count) {
|
||||
|
||||
if (offset + count > this.words.length || srcOffset >= src.length || count < 0 || offset < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
System.arraycopy(src, srcOffset, this.words, offset, count);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value at an address
|
||||
*
|
||||
* @param address address of the value to change
|
||||
* @param value 16-bit value to set
|
||||
*/
|
||||
@Override
|
||||
public void set(int address, int value) {
|
||||
address = (char) address;
|
||||
|
||||
if (address >= words.length) {
|
||||
LogManager.LOGGER.info("DEBUG: Trying to set memory out of bounds: " + address);
|
||||
return;
|
||||
}
|
||||
|
||||
words[address] = (char) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configurably corrupt memory
|
||||
*
|
||||
* @param blockSize Block size (in words) in which to randomly flip one bit
|
||||
*/
|
||||
public void corrupt(int blockSize) {
|
||||
Random rand = new Random();
|
||||
|
||||
// Increment offset by blockSize
|
||||
for (int offset = 0; offset < words.length; offset += blockSize) {
|
||||
|
||||
// Calculate address to corrupt by adding a random value between 0 to (blocksize-1) to offset
|
||||
int address = rand.nextInt(blockSize) + offset;
|
||||
|
||||
// Checking here avoids having a protected area at the end of the address space
|
||||
if(address < words.length) {
|
||||
|
||||
// Calculate bitmask by left-shifting 1 by a random value between 0 and 15
|
||||
int bitmask = 1 << rand.nextInt(16);
|
||||
|
||||
// Flip the bit with XOR
|
||||
words[address] ^= bitmask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the memory with 0s
|
||||
*/
|
||||
public void clear() {
|
||||
Arrays.fill(words, (char) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get byte array of the Memory object
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
|
||||
byte[] bytes = new byte[words.length * 2];
|
||||
ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asCharBuffer().put(words);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
|
||||
Document dbObject = new Document();
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
Deflater compressor = new Deflater(Deflater.BEST_SPEED, true);
|
||||
DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(stream, compressor);
|
||||
deflaterOutputStream.write(getBytes());
|
||||
deflaterOutputStream.close();
|
||||
byte[] compressedBytes = stream.toByteArray();
|
||||
|
||||
dbObject.put("zipBytes", new String(Base64.getEncoder().encode(compressedBytes)));
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.words = new char[bytes.length / 2];
|
||||
ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asCharBuffer().get(this.words);
|
||||
}
|
||||
|
||||
public char[] getWords() {
|
||||
return words;
|
||||
}
|
||||
}
|
||||
276
src/main/java/net/simon987/mar/server/assembly/Operand.java
Executable file
276
src/main/java/net/simon987/mar/server/assembly/Operand.java
Executable file
@@ -0,0 +1,276 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
import net.simon987.mar.server.assembly.exception.InvalidOperandException;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Represents an operand of an instruction. An operand can refer to a
|
||||
* location in memory, a register or an immediate value
|
||||
*/
|
||||
public class Operand {
|
||||
|
||||
public static final int IMMEDIATE_VALUE = 0b11111; //1 1111
|
||||
|
||||
public static final int IMMEDIATE_VALUE_MEM = 0b11110; //1 1110
|
||||
|
||||
/**
|
||||
* The actual text of the operand (e.g. "[AX]")
|
||||
*/
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* Type of the operand
|
||||
*/
|
||||
private OperandType type;
|
||||
|
||||
/**
|
||||
* Value of the the operand, this is the part that will
|
||||
* written into the instruction.
|
||||
*/
|
||||
private int value = 0;
|
||||
|
||||
/**
|
||||
* Data of the operand. This will be appended after the instruction.
|
||||
* For example, "[AX+3]" value={index of AX] + {number of registers}, Data=3
|
||||
*/
|
||||
private int data = 0;
|
||||
|
||||
public Operand(OperandType type, int value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operand(OperandType type, int value, int data) {
|
||||
this(type, value);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Operand from text. It assumes that the numerical values that can't be
|
||||
* parsed are labels that are not defined yet.
|
||||
*
|
||||
* @param text Text of the operand
|
||||
* @param line Line of the instruction. Will be used to report exceptions
|
||||
*/
|
||||
public Operand(String text, RegisterSet registerSet, int line) throws InvalidOperandException {
|
||||
this(text, null, registerSet, line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Operand from text. If labels is not null, it will be used to parse the
|
||||
* operand.
|
||||
*
|
||||
* @param text Text of the operand
|
||||
* @param labels Map of labels
|
||||
* @param line Line of the instruction. Will be used to report exceptions
|
||||
*/
|
||||
public Operand(String text, HashMap<String, Character> labels, RegisterSet registerSet, int line)
|
||||
throws InvalidOperandException {
|
||||
|
||||
this.text = text.replace(",", "");
|
||||
this.text = this.text.trim();
|
||||
|
||||
|
||||
if (!parseImmediate(this.text) && !parseReg(this.text, registerSet) && !parseLabel(this.text, labels)) {
|
||||
if (this.text.startsWith("[") && this.text.endsWith("]")) {
|
||||
|
||||
//Remove []s
|
||||
this.text = this.text.substring(1, this.text.length() - 1);
|
||||
|
||||
if (parseImmediate(this.text) || parseLabel(this.text, labels)) {
|
||||
//Operand refers to memory
|
||||
type = OperandType.MEMORY_IMM16;
|
||||
value = Operand.IMMEDIATE_VALUE_MEM;
|
||||
|
||||
} else if (!parseRegExpr(registerSet, labels)) {
|
||||
|
||||
if (labels == null) {
|
||||
type = OperandType.MEMORY_IMM16;
|
||||
data = 0;
|
||||
} else {
|
||||
throw new InvalidOperandException("Invalid operand " + this.text, line);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
if (labels == null) {
|
||||
type = OperandType.IMMEDIATE16;
|
||||
data = 0;
|
||||
} else {
|
||||
throw new InvalidOperandException("Invalid operand " + this.text, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to parse an integer
|
||||
*
|
||||
* @param text Text to parse, can be a label or immediate value (hex or dec)
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
private boolean parseImmediate(String text) {
|
||||
|
||||
text = text.trim();
|
||||
|
||||
try {
|
||||
//Try IMM
|
||||
type = OperandType.IMMEDIATE16;
|
||||
data = Integer.decode(text);
|
||||
value = IMMEDIATE_VALUE;
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
//Try Binary number (format 0bXXXX)
|
||||
if (text.startsWith("0b")) {
|
||||
try {
|
||||
data = Integer.parseInt(text.substring(2), 2);
|
||||
value = IMMEDIATE_VALUE;
|
||||
return true;
|
||||
} catch (NumberFormatException e2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a user-defined label
|
||||
*
|
||||
* @param text Text to parse
|
||||
* @param labels Map of labels
|
||||
* @return true if parsing is successful, false otherwise
|
||||
*/
|
||||
private boolean parseLabel(String text, HashMap<String, Character> labels) {
|
||||
|
||||
text = text.trim();
|
||||
|
||||
if (labels == null) {
|
||||
return false;
|
||||
} else if (labels.containsKey(text)) {
|
||||
type = OperandType.IMMEDIATE16;
|
||||
data = labels.get(text);
|
||||
value = IMMEDIATE_VALUE;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a register
|
||||
*
|
||||
* @param text Text to parse
|
||||
* @return true if successful
|
||||
*/
|
||||
private boolean parseReg(String text, RegisterSet registerSet) {
|
||||
|
||||
int index = registerSet.getIndex(text.trim());
|
||||
|
||||
if (index != -1) {
|
||||
value = index;
|
||||
type = OperandType.REGISTER16;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a register followed by an expression.
|
||||
* The expression has to follow this format: REGISTER+X or REGISTER-X. Any amount of white space between
|
||||
* the terms will be ignored.
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
private boolean parseRegExpr(RegisterSet registerSet, HashMap<String, Character> labels) {
|
||||
|
||||
String expr;
|
||||
|
||||
text = text.replaceAll("\\s+", "");
|
||||
|
||||
if (text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.length() >= 2 && parseReg(text.substring(0, 2), registerSet)) {
|
||||
expr = text.substring(2);
|
||||
} else if (parseReg(text.substring(0, 1), registerSet)) {
|
||||
//Starts with 1-char register
|
||||
expr = text.substring(1);
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expr.replaceAll("\\s+", "").isEmpty()) {
|
||||
//No data specified
|
||||
type = OperandType.MEMORY_REG16;
|
||||
value += registerSet.size(); //refers to memory.
|
||||
data = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Remove white space
|
||||
expr = expr.replaceAll("\\s+", "");
|
||||
|
||||
try {
|
||||
type = OperandType.MEMORY_REG_DISP16;
|
||||
|
||||
if (labels != null) {
|
||||
|
||||
Character address = labels.get(expr.replaceAll("[^A-Za-z0-9_]", ""));
|
||||
if (address != null) {
|
||||
data = (expr.startsWith("-")) ? -address : address;
|
||||
value += registerSet.size() * 2;//refers to memory with disp
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//label is invalid
|
||||
data = Integer.decode(expr);
|
||||
value += registerSet.size() * 2; //refers to memory with disp
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
//Integer.decode failed, try binary
|
||||
if (expr.startsWith("+0b")) {
|
||||
try {
|
||||
data = Integer.parseInt(expr.substring(3), 2);
|
||||
value += registerSet.size() * 2; //refers to memory with disp
|
||||
return true;
|
||||
} catch (NumberFormatException e2) {
|
||||
return false;
|
||||
}
|
||||
} else if (expr.startsWith("-0b")) {
|
||||
try {
|
||||
data = -Integer.parseInt(expr.substring(3), 2);
|
||||
value += registerSet.size() * 2; //refers to memory with disp
|
||||
return true;
|
||||
} catch (NumberFormatException e2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public OperandType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
26
src/main/java/net/simon987/mar/server/assembly/OperandType.java
Executable file
26
src/main/java/net/simon987/mar/server/assembly/OperandType.java
Executable file
@@ -0,0 +1,26 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* Types of an operand
|
||||
*/
|
||||
public enum OperandType {
|
||||
|
||||
REGISTER16("16-bit Register"),
|
||||
MEMORY_IMM16("16-bit Memory referred by immediate"),
|
||||
MEMORY_REG16("16-bit Memory referred by register"),
|
||||
MEMORY_REG_DISP16("16-bit Memory referred by register with displacement"),
|
||||
IMMEDIATE16("16-bit Immediate");
|
||||
|
||||
/**
|
||||
* Description of the Operand type
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
OperandType(String desc) {
|
||||
this.description = desc;
|
||||
}
|
||||
}
|
||||
50
src/main/java/net/simon987/mar/server/assembly/Register.java
Executable file
50
src/main/java/net/simon987/mar/server/assembly/Register.java
Executable file
@@ -0,0 +1,50 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* Represents a register in a cpu
|
||||
*/
|
||||
public class Register {
|
||||
|
||||
/**
|
||||
* Name of the register
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 16-bit value of the register
|
||||
*/
|
||||
private char value = 0;
|
||||
|
||||
/**
|
||||
* Create a new Register
|
||||
*
|
||||
* @param name Name of the register, always set in Upper case
|
||||
*/
|
||||
public Register(String name) {
|
||||
this.name = name.toUpperCase();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public char getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value of the register.
|
||||
*
|
||||
* @param value value to set. It is casted to char
|
||||
*/
|
||||
public void setValue(int value) {
|
||||
this.value = (char) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return name + "=" + value;
|
||||
|
||||
}
|
||||
}
|
||||
211
src/main/java/net/simon987/mar/server/assembly/RegisterSet.java
Executable file
211
src/main/java/net/simon987/mar/server/assembly/RegisterSet.java
Executable file
@@ -0,0 +1,211 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
|
||||
import net.simon987.mar.server.io.MongoSerializable;
|
||||
import net.simon987.mar.server.logging.LogManager;
|
||||
import org.bson.Document;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A set of registers for a CPU
|
||||
*/
|
||||
public class RegisterSet implements Target, MongoSerializable {
|
||||
|
||||
/**
|
||||
* List of registers
|
||||
*/
|
||||
private final HashMap<Integer, Register> registers = new HashMap<>(8);
|
||||
|
||||
|
||||
/**
|
||||
* Create an empty Register set
|
||||
*/
|
||||
public RegisterSet() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of a Register by its name
|
||||
* This method assumes that the
|
||||
*
|
||||
* @param name Name of the register
|
||||
* @return index of the register, -1 if not found
|
||||
*/
|
||||
int getIndex(String name) {
|
||||
|
||||
name = name.toUpperCase();
|
||||
|
||||
for (Integer i : registers.keySet()) {
|
||||
if (registers.get(i).getName().equals(name)) {
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a register by its index
|
||||
*
|
||||
* @param index index of the register
|
||||
*/
|
||||
public Register getRegister(int index) {
|
||||
return registers.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a register by its name (e.g. "AX")
|
||||
*
|
||||
* @param name Name of the register, case insensitive
|
||||
*/
|
||||
public Register getRegister(String name) {
|
||||
|
||||
name = name.toUpperCase();
|
||||
|
||||
for (Register r : registers.values()) {
|
||||
if (r.getName().equals(name)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a register
|
||||
*
|
||||
* @param address Address of the value. Can refer to a memory address or the index
|
||||
* of a register
|
||||
* @return 16-bit value of a register
|
||||
*/
|
||||
@Override
|
||||
public int get(int address) {
|
||||
|
||||
Register register = registers.get(address);
|
||||
|
||||
if (register != null) {
|
||||
return register.getValue();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a register
|
||||
*
|
||||
* @param address index of the value to change
|
||||
* @param value value to set
|
||||
*/
|
||||
@Override
|
||||
public void set(int address, int value) {
|
||||
|
||||
Register register = registers.get(address);
|
||||
|
||||
if (register != null) {
|
||||
register.setValue(value);
|
||||
} else {
|
||||
LogManager.LOGGER.info("DEBUG: trying to set unknown reg index : " + address);
|
||||
}
|
||||
}
|
||||
|
||||
public void put(int index, Register register) {
|
||||
registers.put(index, register);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (Register register : registers.values()) {
|
||||
register.setValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a register to the register set
|
||||
* <p>
|
||||
* the register set will break if the indexes of the registers
|
||||
* are not consecutive, starting at address 1.
|
||||
*
|
||||
* @param index Index of the register
|
||||
* @param reg Register to add
|
||||
*/
|
||||
void addRegister(int index, Register reg) {
|
||||
registers.put(index, reg);
|
||||
}
|
||||
|
||||
int size() {
|
||||
return registers.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Document mongoSerialise() {
|
||||
List<Document> registers = new ArrayList<>();
|
||||
for (Integer index : this.registers.keySet()) {
|
||||
Document register = new Document();
|
||||
|
||||
register.put("index", index);
|
||||
register.put("name", getRegister(index).getName());
|
||||
register.put("value", (int) getRegister(index).getValue());
|
||||
|
||||
registers.add(register);
|
||||
}
|
||||
|
||||
Document obj = new Document();
|
||||
obj.put("registers", registers);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static RegisterSet deserialize(Document obj) {
|
||||
|
||||
RegisterSet registerSet = new RegisterSet();
|
||||
|
||||
List registers = (ArrayList) obj.get("registers");
|
||||
|
||||
for (Object sRegister : registers) {
|
||||
|
||||
Register register = new Register((String) ((Document) sRegister).get("name"));
|
||||
register.setValue(((Document) sRegister).getInteger("value"));
|
||||
|
||||
registerSet.registers.put(((Document) sRegister).getInteger("index"), register);
|
||||
|
||||
}
|
||||
|
||||
return registerSet;
|
||||
}
|
||||
|
||||
public static RegisterSet deserialize(JSONObject json) {
|
||||
|
||||
RegisterSet registerSet = new RegisterSet();
|
||||
|
||||
JSONArray registers = (JSONArray) json.get("registers");
|
||||
|
||||
for (JSONObject jsonRegister : (ArrayList<JSONObject>) registers) {
|
||||
|
||||
Register register = new Register((String) jsonRegister.get("name"));
|
||||
register.setValue((int) (long) jsonRegister.get("value"));
|
||||
|
||||
registerSet.registers.put((int) (long) jsonRegister.get("index"), register);
|
||||
|
||||
}
|
||||
|
||||
return registerSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String str = "";
|
||||
|
||||
for (Integer index : registers.keySet()) {
|
||||
str += index + " " + registers.get(index).getName() + "=" + Util.toHex(registers.get(index).getValue()) + "\n";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
20
src/main/java/net/simon987/mar/server/assembly/Section.java
Executable file
20
src/main/java/net/simon987/mar/server/assembly/Section.java
Executable file
@@ -0,0 +1,20 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* Section of a user-created program.
|
||||
* The execution will start at the beginning of the code
|
||||
* segment.
|
||||
*/
|
||||
public enum Section {
|
||||
|
||||
/**
|
||||
* Code section of the program. Contains executable code
|
||||
*/
|
||||
TEXT,
|
||||
|
||||
/**
|
||||
* Data section of the program. Contains initialised data
|
||||
*/
|
||||
DATA
|
||||
|
||||
}
|
||||
151
src/main/java/net/simon987/mar/server/assembly/Status.java
Executable file
151
src/main/java/net/simon987/mar/server/assembly/Status.java
Executable file
@@ -0,0 +1,151 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* Represents the state of the processor
|
||||
*/
|
||||
public class Status {
|
||||
|
||||
/**
|
||||
* Set to true when the result of
|
||||
* an 8/16bit operation is too big to fit
|
||||
* in a 8/16bit register or memory
|
||||
*/
|
||||
private boolean carryFlag = false;
|
||||
|
||||
/**
|
||||
* Set to true when the result of an operation
|
||||
* is equal to zero
|
||||
*/
|
||||
private boolean zeroFlag;
|
||||
|
||||
/**
|
||||
* Set to true when the most significant
|
||||
* (highest) bit of the result is set
|
||||
*/
|
||||
private boolean signFlag = false;
|
||||
|
||||
/**
|
||||
* Set to true when the result of
|
||||
* a signed operation
|
||||
* * (+) + (+) = (-)
|
||||
* * (-) + (-) = (+)
|
||||
*/
|
||||
private boolean overflowFlag = false;
|
||||
|
||||
/**
|
||||
* CPU execution will stop when this flag
|
||||
* is set
|
||||
*/
|
||||
private boolean breakFlag = false;
|
||||
|
||||
/**
|
||||
* Set when an error occurred (division by 0, )
|
||||
*/
|
||||
private boolean errorFlag = false;
|
||||
|
||||
/**
|
||||
* Unset all flags
|
||||
*/
|
||||
public void clear() {
|
||||
carryFlag = false;
|
||||
zeroFlag = false;
|
||||
signFlag = false;
|
||||
overflowFlag = false;
|
||||
breakFlag = false;
|
||||
errorFlag = false;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "" + (signFlag ? 1 : 0) + ' ' +
|
||||
(zeroFlag ? 1 : 0) + ' ' +
|
||||
(carryFlag ? 1 : 0) + ' ' +
|
||||
(overflowFlag ? 1 : 0) + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when the result of
|
||||
* an 8/16bit operation is too big to fit
|
||||
* in a 8/16bit register or memory
|
||||
*/
|
||||
public boolean isCarryFlag() {
|
||||
return carryFlag;
|
||||
}
|
||||
|
||||
public void setCarryFlag(boolean carryFlag) {
|
||||
this.carryFlag = carryFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when the result of an operation
|
||||
* is equal to zero
|
||||
*/
|
||||
public boolean isZeroFlag() {
|
||||
return zeroFlag;
|
||||
}
|
||||
|
||||
public void setZeroFlag(boolean zeroFlag) {
|
||||
this.zeroFlag = zeroFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when the most significant
|
||||
* (highest) bit of the result is set
|
||||
*/
|
||||
public boolean isSignFlag() {
|
||||
return signFlag;
|
||||
}
|
||||
|
||||
public void setSignFlag(boolean signFlag) {
|
||||
this.signFlag = signFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when the result of
|
||||
* a signed operation
|
||||
* * (+) + (+) = (-)
|
||||
* * (-) + (-) = (+)
|
||||
*/
|
||||
public boolean isOverflowFlag() {
|
||||
return overflowFlag;
|
||||
}
|
||||
|
||||
public void setOverflowFlag(boolean overflowFlag) {
|
||||
this.overflowFlag = overflowFlag;
|
||||
}
|
||||
|
||||
/**
|
||||
* CPU execution will stop when this flag
|
||||
* is set
|
||||
*/
|
||||
public boolean isBreakFlag() {
|
||||
return breakFlag;
|
||||
}
|
||||
|
||||
public void setBreakFlag(boolean breakFlag) {
|
||||
this.breakFlag = breakFlag;
|
||||
}
|
||||
|
||||
public boolean isErrorFlag() {
|
||||
return errorFlag;
|
||||
}
|
||||
|
||||
public void setErrorFlag(boolean errorFlag) {
|
||||
this.errorFlag = errorFlag;
|
||||
}
|
||||
|
||||
public char toByte() {
|
||||
char stat = 0;
|
||||
stat = (char) (stat | ((signFlag ? 1 : 0) << 3));
|
||||
stat = (char) (stat | ((zeroFlag ? 1 : 0) << 2));
|
||||
stat = (char) (stat | ((carryFlag ? 1 : 0) << 1));
|
||||
stat = (char) (stat | (overflowFlag ? 1 : 0));
|
||||
return stat;
|
||||
}
|
||||
|
||||
public void fromByte(char stat) {
|
||||
setSignFlag((stat & (1 << 3)) != 0);
|
||||
setZeroFlag((stat & (1 << 2)) != 0);
|
||||
setCarryFlag((stat & (1 << 1)) != 0);
|
||||
setOverflowFlag((stat & 1) != 0);
|
||||
}
|
||||
}
|
||||
30
src/main/java/net/simon987/mar/server/assembly/Target.java
Executable file
30
src/main/java/net/simon987/mar/server/assembly/Target.java
Executable file
@@ -0,0 +1,30 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* A Target is a location that can be read and written to during
|
||||
* the execution of an instruction.
|
||||
* <p>
|
||||
* For example: MOV dstTARGET, srcTARGET
|
||||
* <p>
|
||||
* A target is usually Memory or Register
|
||||
*/
|
||||
public interface Target {
|
||||
|
||||
/**
|
||||
* Get a value from a Target at an address.
|
||||
*
|
||||
* @param address Address of the value. Can refer to a memory address or the index
|
||||
* of a register
|
||||
* @return value at specified address
|
||||
*/
|
||||
int get(int address);
|
||||
|
||||
/**
|
||||
* Set a value at an address
|
||||
*
|
||||
* @param address address of the value to change
|
||||
* @param value value to set
|
||||
*/
|
||||
void set(int address, int value);
|
||||
|
||||
}
|
||||
115
src/main/java/net/simon987/mar/server/assembly/Util.java
Executable file
115
src/main/java/net/simon987/mar/server/assembly/Util.java
Executable file
@@ -0,0 +1,115 @@
|
||||
package net.simon987.mar.server.assembly;
|
||||
|
||||
/**
|
||||
* Set of utility functions related to assembly language parsing and execution
|
||||
*/
|
||||
public class Util {
|
||||
|
||||
/**
|
||||
* Get the lower byte of a word
|
||||
*/
|
||||
static byte getLowerByte(char value) {
|
||||
return (byte) (value & 0x00FF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the higher byte of a word
|
||||
*/
|
||||
public static byte getHigherByte(char value) {
|
||||
return (byte) ((value >> 8) & 0x00FF);
|
||||
}
|
||||
|
||||
public static char getHigherWord(int value) {
|
||||
return (char) ((value >> 16) & 0x0000FFFF);
|
||||
}
|
||||
|
||||
public static char getLowerWord(int value) {
|
||||
return (char) (value & 0x0000FFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a 32bit value to a unsigned 8bit value
|
||||
*/
|
||||
public static int uByte(int b) {
|
||||
return (b) & 0x00FF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a 32bit value to a unsigned 16bit value
|
||||
*/
|
||||
private static int uShort(int s) {
|
||||
return s & 0x0000FFFF;
|
||||
}
|
||||
|
||||
public static String toHex(int a) {
|
||||
return String.format("%04X ", a);
|
||||
}
|
||||
|
||||
public static String toHex(byte[] byteArray) {
|
||||
|
||||
String result = "";
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (byte b : byteArray) {
|
||||
result += String.format("%02X ", b);
|
||||
if (count == 16) {
|
||||
count = -1;
|
||||
result += "\n";
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a 16bit value is negative using two's complement representation
|
||||
*
|
||||
* @param result 16bit integer
|
||||
*/
|
||||
public static boolean checkSign16(int result) {
|
||||
result = Util.uShort(result);
|
||||
return (result >> 15) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the overflow flag should be set for an
|
||||
* add operation.
|
||||
*
|
||||
* @param a summand
|
||||
* @param b summand
|
||||
*/
|
||||
public static boolean checkOverFlowAdd16(int a, int b) {
|
||||
boolean aSign = (Util.uShort(a) >> 15) == 1;
|
||||
boolean bSign = (Util.uShort(b) >> 15) == 1;
|
||||
|
||||
return aSign == bSign && aSign != checkSign16(a + b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the overflow flag should be set for a
|
||||
* sub operation.
|
||||
*
|
||||
* @param a minuend
|
||||
* @param b subtrahend
|
||||
*/
|
||||
public static boolean checkOverFlowSub16(int a, int b) {
|
||||
return checkOverFlowAdd16(a, -b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the carry flag should be set
|
||||
*
|
||||
* @param result Result of a 16bit operation
|
||||
*/
|
||||
public static boolean checkCarry16(int result) {
|
||||
return ((result) & 0x10000) == 0x10000; //Check if 17th bit is set
|
||||
}
|
||||
|
||||
|
||||
public static int manhattanDist(int x1, int y1, int x2, int y2) {
|
||||
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.simon987.mar.server.assembly.exception;
|
||||
|
||||
/**
|
||||
* Threw when a problem is encountered while parsing a line
|
||||
* of a user's code, making it impossible to translate it to
|
||||
* binary code.
|
||||
*/
|
||||
public class AssemblyException extends Exception {
|
||||
|
||||
/**
|
||||
* Line offset in the user's code.
|
||||
*/
|
||||
private final int line;
|
||||
|
||||
/**
|
||||
* Create a new Assembly Exception
|
||||
*
|
||||
* @param msg Message of the exception
|
||||
* @param line Line offset in the user's code.
|
||||
*/
|
||||
public AssemblyException(String msg, int line) {
|
||||
super(msg);
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.simon987.mar.server.assembly.exception;
|
||||
|
||||
public class CancelledException extends Exception {
|
||||
public CancelledException() {
|
||||
super("CPU Initialisation was cancelled");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.simon987.mar.server.assembly.exception;
|
||||
|
||||
/**
|
||||
* Threw when a user attempts to define the same section twice
|
||||
*/
|
||||
public class DuplicateSectionException extends AssemblyException {
|
||||
|
||||
/**
|
||||
* Message of the exception
|
||||
*/
|
||||
private static final String message = "Sections can only be defined once";
|
||||
|
||||
/**
|
||||
* Create a new Duplicate Section Exception
|
||||
*/
|
||||
public DuplicateSectionException(int line) {
|
||||
super(message, line);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.simon987.mar.server.assembly.exception;
|
||||
|
||||
/**
|
||||
* Threw when the parser encounters an empty line
|
||||
*/
|
||||
public class EmptyLineException extends AssemblyException {
|
||||
public EmptyLineException(int line) {
|
||||
super("Encountered empty line", line);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.simon987.mar.server.assembly.exception;
|
||||
|
||||
/**
|
||||
* Class of exceptions that should stop assembly immediately
|
||||
*/
|
||||
public class FatalAssemblyException extends AssemblyException {
|
||||
|
||||
/**
|
||||
* Message of the exception
|
||||
*/
|
||||
private static final String message = "A fatal assembly error has occurred";
|
||||
|
||||
/**
|
||||
* Create a new Duplicate Section Exception
|
||||
*/
|
||||
public FatalAssemblyException(String msg, int line) {
|
||||
super(msg, line);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user