Hacked NPC minimum viable

This commit is contained in:
simon
2018-12-22 11:26:34 -05:00
parent 955d61ce99
commit e4a06e79d4
26 changed files with 1030 additions and 161 deletions

View File

@@ -1,17 +1,19 @@
package net.simon987.npcplugin;
import net.simon987.server.GameServer;
import net.simon987.server.game.objects.MessageReceiver;
import net.simon987.server.game.objects.Structure;
import net.simon987.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 {
public class Factory extends Structure implements Updatable, MessageReceiver {
private static final int MAP_INFO = 0x0401;
@@ -30,6 +32,15 @@ public class Factory extends Structure implements Updatable {
*/
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;
public Factory() {
super(2, 2);
}
@@ -57,14 +68,7 @@ public class Factory extends Structure implements Updatable {
Point p = getAdjacentTile();
if (p != null) {
NonPlayerCharacter npc = new HarvesterNPC();
npc.setWorld(getWorld());
npc.setObjectId(new ObjectId());
npc.setX(p.x);
npc.setY(p.y);
getWorld().addObject(npc);
getWorld().incUpdatable();
NonPlayerCharacter npc = spawnNPC(p);
settlement.addNpc(npc);
}
}
@@ -75,4 +79,65 @@ public class Factory extends Structure implements Updatable {
cooldown--;
}
}
private NonPlayerCharacter spawnNPC(Point p) {
NonPlayerCharacter npc;
if (programIndex == 0) {
npc = new HarvesterNPC();
npc.setWorld(getWorld());
npc.setObjectId(new ObjectId());
npc.setX(p.x);
npc.setY(p.y);
getWorld().addObject(npc);
getWorld().incUpdatable();
} else {
npc = new HackedNPC(program);
npc.setWorld(getWorld());
npc.setObjectId(new ObjectId());
npc.setX(p.x);
npc.setY(p.y);
getWorld().addObject(npc);
getWorld().incUpdatable();
System.out.println("NEW HACKED NPC");
this.locked = true;
}
return npc;
}
@Override
public boolean sendMessage(char[] message) {
String strMessage = String.valueOf(message);
System.out.println("Received message " + strMessage);
if (locked) {
Settlement settlement = NpcPlugin.settlementMap.get(getWorld().getId());
if (Arrays.equals(settlement.getPassword(), message)) {
System.out.println("Factory unlock");
this.locked = false;
return true;
}
System.out.println("Wrong password, " + strMessage + "!=" + String.valueOf(settlement.getPassword()));
} else if (programIndex <= 2048) { //todo config
if (programIndex == 0) {
program = new char[2048];
}
System.arraycopy(message, 0, program, programIndex, message.length);
System.out.println("Factory append code: " + strMessage);
System.out.println("Wrote " + message.length + " chars");
programIndex += message.length;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,314 @@
package net.simon987.npcplugin;
import net.simon987.server.GameServer;
import net.simon987.server.assembly.*;
import net.simon987.server.game.item.Item;
import net.simon987.server.game.item.ItemVoid;
import net.simon987.server.game.objects.Action;
import net.simon987.server.game.objects.ControllableUnit;
import net.simon987.server.game.objects.Direction;
import net.simon987.server.game.objects.HardwareHost;
import net.simon987.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, HardwareHost {
private static final int MEM_SIZE = GameServer.INSTANCE.getConfig().getInt("hacked_npc_mem_size");
private CPU cpu;
/**
* List of attached hardware, 'modules'
*/
private Map<Integer, HardwareModule> hardwareAddresses = new HashMap<>();
private Map<Class<? extends HardwareModule>, Integer> hardwareModules = new HashMap<>();
private Action currentAction = Action.IDLE;
private Action lastAction = Action.IDLE;
private ArrayList<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);
//Write program
boolean write = cpu.getMemory().write(0, program, 0, program.length);
System.out.println("Write " + write);
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"));
}
}
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"));
}
}
@Override
public void update() {
System.out.println(Util.toHex(cpu.getMemory().getBytes()));
//Execute code
System.out.println("HACKED NPC " + this.getObjectId());
int timeout = Math.min(getEnergy(), 30); //todo get from config
cpu.reset();
int cost = cpu.execute(timeout);
spendEnergy(cost);
if (currentAction == Action.WALKING) {
if (spendEnergy(100)) {
if (!incrementLocation()) {
//Couldn't walk
currentAction = Action.IDLE;
}
} 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;
lastConsoleMessagesBuffer = new ArrayList<>(consoleMessagesBuffer);
consoleMessagesBuffer.clear();
for (HardwareModule module : hardwareAddresses.values()) {
module.update();
}
}
@Override
public void setKeyboardBuffer(ArrayList<Integer> kbBuffer) {
}
@Override
public void setParent(User user) {
}
@Override
public User getParent() {
return null;
}
@Override
public ArrayList<Integer> getKeyboardBuffer() {
return null;
}
@Override
public Memory getFloppyData() {
return null;
}
@Override
public void setAction(Action action) {
currentAction = action;
}
@Override
public ArrayList<char[]> getConsoleMessagesBuffer() {
return null;
}
@Override
public int getConsoleMode() {
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);
}
public boolean spendEnergy(int amount) {
NpcBattery battery = (NpcBattery) getHardware(NpcBattery.class);
if (battery.getEnergy() - amount < 0) {
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 JSONObject debugJsonSerialise() {
return jsonSerialise();
}
}

View File

@@ -0,0 +1,109 @@
package net.simon987.npcplugin;
import net.simon987.server.GameServer;
import net.simon987.server.assembly.HardwareModule;
import net.simon987.server.assembly.Status;
import net.simon987.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;
}
}

View File

@@ -0,0 +1,109 @@
package net.simon987.npcplugin;
import net.simon987.server.GameServer;
import net.simon987.server.assembly.HardwareModule;
import net.simon987.server.assembly.Status;
import net.simon987.server.game.item.Item;
import net.simon987.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);
}
}

View File

@@ -13,13 +13,17 @@ import net.simon987.server.logging.LogManager;
import net.simon987.server.plugin.ServerPlugin;
import org.bson.Document;
import java.io.InputStream;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
public class NpcPlugin extends ServerPlugin {
public static Map<String, Settlement> settlementMap;
public static Document DEFAULT_HACKED_NPC;
@Override
public void init(GameServer gameServer) {
@@ -39,14 +43,22 @@ public class NpcPlugin extends ServerPlugin {
registry.registerGameObject(ElectricBox.class);
registry.registerGameObject(Portal.class);
registry.registerGameObject(VaultExitPortal.class);
registry.registerGameObject(HackedNPC.class);
registry.registerHardware(RadioReceiverHardware.class);
registry.registerHardware(NpcBattery.class);
registry.registerHardware(NpcInventory.class);
registry.registerTile(TileVaultFloor.ID, TileVaultFloor.class);
registry.registerTile(TileVaultWall.ID, TileVaultWall.class);
settlementMap = new ConcurrentHashMap<>();
InputStream is = getClass().getClassLoader().getResourceAsStream("defaultHackedCubotHardware.json");
Scanner scanner = new Scanner(is).useDelimiter("\\A");
String json = scanner.next();
DEFAULT_HACKED_NPC = Document.parse(json);
LogManager.LOGGER.info("(NPC Plugin) Initialised NPC plugin");
}

View File

@@ -59,6 +59,7 @@ public class Settlement implements MongoSerializable {
this.world = world;
this.difficultyLevel = DifficultyLevel.NORMAL; //TODO randomize ?
this.password = "12345678".toCharArray();
outerLoopFactory:
for (int x = 2; x < 12; x++) {
@@ -175,7 +176,7 @@ public class Settlement implements MongoSerializable {
}
document.put("factory", factory.getObjectId());
document.put("difficulty_level", difficultyLevel.ordinal());
document.put("password", "1234567"); //todo
document.put("password", String.valueOf(password));
List<ObjectId> npcArray = new ArrayList<>(npcs.size());

View File

@@ -0,0 +1,62 @@
{
"hardware": [
{
"type": "net.simon987.cubotplugin.CubotLeg",
"address": 1
},
{
"type": "net.simon987.cubotplugin.CubotLaser",
"address": 2
},
{
"type": "net.simon987.cubotplugin.CubotLidar",
"address": 3
},
{
"type": "net.simon987.cubotplugin.CubotDrill",
"address": 5
},
{
"type": "net.simon987.npcplugin.NpcInventory",
"item": null,
"address": 6
},
{
"type": "net.simon987.mischwplugin.RandomNumberGenerator",
"address": 7
},
{
"type": "net.simon987.mischwplugin.Clock",
"address": 8
},
{
"type": "net.simon987.cubotplugin.CubotHologram",
"color": 0,
"value": 0,
"string": "",
"mode": 0,
"address": 9
},
{
"type": "net.simon987.npcplugin.NpcBattery",
"energy": 60000,
"max_energy": 60000,
"address": 262
},
{
"type": "net.simon987.npcplugin.RadioReceiverHardware",
"cubot": {
"$oid": "5c1d43e40d3d2530aba636df"
},
"address": 12
},
{
"type": "net.simon987.cubotplugin.CubotComPort",
"address": 13
},
{
"type": "net.simon987.cubotplugin.CubotCore",
"address": 14
}
]
}

View File

@@ -1,3 +1,4 @@
classpath=net.simon987.npcplugin.NpcPlugin
name=NPC Plugin
version=1.1
version=1.1
depend=Cubot Plugin