Stop AI from controlling enemy in online sessions

Also added bidirectional data transfer to online battles so that I can
send damage requests to the opponent when needed.
This commit is contained in:
MichaelTheShifter 2016-06-29 18:36:17 -04:00
parent dec9dfac5e
commit 20fcece26d
4 changed files with 230 additions and 34 deletions

View file

@ -168,6 +168,16 @@ public void LaunchAttack(AttackType type, int rate)
public string Hostname { get; set; } public string Hostname { get; set; }
internal void throw_repaired()
{
OnRepair?.Invoke(this, new EventArgs());
}
internal void throw_damaged()
{
HP_Decreased?.Invoke(this, new EventArgs());
}
public event EventHandler HP_Decreased; public event EventHandler HP_Decreased;
public void Deteriorate(int amount) public void Deteriorate(int amount)

View file

@ -24,7 +24,9 @@ public HackUI()
private bool InOnlineBattle = false; private bool InOnlineBattle = false;
private Online.Hacking.NetTransmitter transmitter = null; private Online.Hacking.NetTransmitter transmitter = null;
private Online.Hacking.NetTransmitter _playerTransmitter = null;
private Online.Hacking.NetListener receiver = null; private Online.Hacking.NetListener receiver = null;
private Online.Hacking.NetListener player_listener = null;
public HackUI(EnemyHacker enemy) public HackUI(EnemyHacker enemy)
{ {
@ -32,11 +34,13 @@ public HackUI(EnemyHacker enemy)
InitializeComponent(); InitializeComponent();
} }
public HackUI(Online.Hacking.NetTransmitter t, Online.Hacking.NetListener l) public HackUI(Online.Hacking.NetTransmitter t, Online.Hacking.NetListener l, Online.Hacking.NetListener playerListener, Online.Hacking.NetTransmitter playerTransmitter)
{ {
InOnlineBattle = true; InOnlineBattle = true;
transmitter = t; transmitter = t;
receiver = l; receiver = l;
player_listener = playerListener;
_playerTransmitter = playerTransmitter;
InitializeComponent(); InitializeComponent();
} }
@ -74,6 +78,8 @@ private void LoadPlayerScreen()
{ {
SetupTutorialUI(0); SetupTutorialUI(0);
} }
if (InOnlineBattle)
LoadOnlinePlayer();
} }
private void VisualizePlayerNetwork() private void VisualizePlayerNetwork()
@ -101,7 +107,60 @@ private void VisualizePlayerNetwork()
AddModule(c); AddModule(c);
} }
} }
private void player_listener_ModuleRemoved(object sender, Online.Hacking.Events.ModuleRemoved e)
{
Computer c = null;
foreach (var m in AllPlayerComputers)
{
if (m.Hostname == e.new_module)
{
c = m;
}
}
AllPlayerComputers.Remove(c);
c.Dispose();
}
private void player_listener_ModuleHealthSet(object sender, Online.Hacking.Events.Health e)
{
var mod = new Computer();
foreach (var m in AllPlayerComputers)
{
if (m.Hostname == e.host_name)
mod = m;
}
mod.HP = e.health;
int old_hp = mod.HP;
mod.HP = e.health;
if (mod.HP > old_hp)
{
mod.throw_repaired();
}
else
{
mod.throw_damaged();
}
}
public void LoadOnlinePlayer()
{
//register event handlers
player_listener.ModuleHealthSet += player_listener_ModuleHealthSet;
player_listener.ModuleRemoved += player_listener_ModuleRemoved;
player_listener.ModuleDisabled += (o, e) =>
{
foreach (var c in AllPlayerComputers)
{
if (c.Hostname == e.hostName)
{
c.Disable();
}
}
};
}
public List<Computer> AllPlayerComputers = null; public List<Computer> AllPlayerComputers = null;
private void tmrplayerhealthdetect_Tick(object sender, EventArgs e) private void tmrplayerhealthdetect_Tick(object sender, EventArgs e)
@ -1360,7 +1419,7 @@ private void Enemy_System_Repaired(object s, EventArgs e)
lbenemycompromised.Location = new Point(location, y); lbenemycompromised.Location = new Point(location, y);
lbenemycompromised.Show(); lbenemycompromised.Show();
c.Flash(lbenemycompromised); c.Flash(lbenemycompromised);
_playerTransmitter?.send_message(Online.Hacking.NetTransmitter.Messages.SetHealth, $"{c.Hostname} {c.HP}");
} }
@ -1375,6 +1434,7 @@ private void Enemy_System_Damaged(object s, EventArgs e)
lbenemycompromised.Location = new Point(location, y); lbenemycompromised.Location = new Point(location, y);
lbenemycompromised.Show(); lbenemycompromised.Show();
c.Flash(lbenemycompromised); c.Flash(lbenemycompromised);
_playerTransmitter?.send_message(Online.Hacking.NetTransmitter.Messages.SetHealth, $"{c.Hostname} {c.HP}");
} }
private decimal TotalEnemyHP = 0; private decimal TotalEnemyHP = 0;
@ -1390,44 +1450,46 @@ private void tmrenemyhealthdetect_Tick(object sender, EventArgs e)
lbcodepoints.Text = $"Codepoints: {API.Codepoints}"; lbcodepoints.Text = $"Codepoints: {API.Codepoints}";
var rnd = new Random(); var rnd = new Random();
int chance = 0; int chance = 0;
foreach (var pc in AllEnemyComputers) if (!InOnlineBattle)
{ {
if (pc.Disabled == false) foreach (var pc in AllEnemyComputers)
{ {
var elist = new List<Computer>(); if (pc.Disabled == false)
if (pc.Enslaved)
elist = AllEnemyComputers;
else
elist = AllPlayerComputers;
foreach (var enemy in elist)
{ {
chance = rnd.Next(1, 20); var elist = new List<Computer>();
if (chance == 10) if (pc.Enslaved)
elist = AllEnemyComputers;
else
elist = AllPlayerComputers;
foreach (var enemy in elist)
{ {
if (IsTutorial) chance = rnd.Next(1, 20);
if (chance == 10)
{ {
if (TutorialProgress == 9) if (IsTutorial)
{ {
ThisPlayerPC.LaunchAttack(pc.GetProperType(), pc.GetDamageRate()); if (TutorialProgress == 9)
} {
else if (TutorialProgress == 32) ThisPlayerPC.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
{ }
enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate()); else if (TutorialProgress == 32)
{
enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
}
else
{
enemy.Enemies.Clear();
}
} }
else else
{ {
enemy.Enemies.Clear(); enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
} }
} }
else
{
enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
}
} }
} }
} }
} }
foreach (var pc in AllEnemyComputers) foreach (var pc in AllEnemyComputers)
{ {
health += (decimal)pc.HP; health += (decimal)pc.HP;
@ -1581,6 +1643,30 @@ public void LoadOnlineEnemy()
} }
} }
}; };
receiver.Won += (o, e) =>
{
//the enemy won!
tmrplayerhealthdetect.Stop();
tmrenemyhealthdetect.Stop();
//dispose all the modules.
while(AllPlayerComputers.Count > 0)
{
AllPlayerComputers[0].Dispose();
AllPlayerComputers.RemoveAt(0);
}
while (AllEnemyComputers.Count > 0)
{
AllEnemyComputers[0].Dispose();
AllEnemyComputers.RemoveAt(0);
}
//Destroy server connection.
Online.Hacking.Matchmaker.DestroySession();
//Display win message.
API.CreateInfoboxSession($"{e.Winner.Name} won.", $"{e.Winner.Name} has overthrown your defenses and compromised your system.", infobox.InfoboxMode.Info);
//Kill the hacker UI.
UserRequestedClose = false;
this.Close();
};
} }
private void Receiver_ModuleRemoved(object sender, Online.Hacking.Events.ModuleRemoved e) private void Receiver_ModuleRemoved(object sender, Online.Hacking.Events.ModuleRemoved e)
@ -1623,7 +1709,16 @@ private void Receiver_ModuleHealthSet(object sender, Online.Hacking.Events.Healt
if (m.Hostname == e.host_name) if (m.Hostname == e.host_name)
mod = m; mod = m;
} }
int old_hp = mod.HP;
mod.HP = e.health; mod.HP = e.health;
if(mod.HP > old_hp)
{
mod.throw_repaired();
}
else
{
mod.throw_damaged();
}
} }
#endregion #endregion

View file

@ -1,4 +1,18 @@
using Newtonsoft.Json; /* ShiftOS Online Hacker Battles - Matchmaker
*
* These classes deal with keeping things in line on the client-side of things.
* They deal with CSP (Client-Side Prediction), sending and receiving messages to
* and from the ShiftOS server, as well as making sure that when you join or leave a
* lobby, the server and other clients actually KNOW you did.
*
* I wouldn't mess with this unless you really, really understand what you're doing,
* as in most cases, modification to the server is required as well (in the case of
* adding new commands). I'd leave modification to the system creator (Michael VanOverbeek) who
* actually wrote this. He's the guy who knows all about how the server works. Wait... why am
* I talking in third person?
*/
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -10,16 +24,35 @@ namespace ShiftOS.Online.Hacking
{ {
public class Matchmaker public class Matchmaker
{ {
//All available lobbies.
public static List<ServerInfo> Servers = null; public static List<ServerInfo> Servers = null;
//All players in the lobby.
public static List<Network> Players = null; public static List<Network> Players = null;
//Some useful info about the lobby in which the player is.
//Also contains server IP to be sent to Package_Grabber.SendMessage().
public static ServerInfo SelectedServer = null; public static ServerInfo SelectedServer = null;
public static Network SelectedNetwork = null;
public static NetListener SelectedNetworkListener = null;
public static NetTransmitter SelectedNetworkTransmitter = null;
//Enemy network information (name, codepoints, etc)
public static Network SelectedNetwork = null;
//There's only one transmitter because generally the player
//won't be interacting with the enemy playfield enough to
//warrent a request to the server.
public static NetListener SelectedNetworkListener = null; //Listen for updates from the opponent.
public static NetTransmitter SelectedNetworkTransmitter = null; //Send messages to the server for enemy updates on opponent clients.
public static NetListener PlayerListener = null; //For receiving non-CSP updates from the server.
public static NetTransmitter SecondaryTransmitter = null; //For sending CSP-created update requests to the opponent (enemy health damage, etc)
//Timer that'll run during matchmaking.
public static Timer MakerTimer = null; public static Timer MakerTimer = null;
/// <summary>
/// This either starts matchmaking or grabs server info. Try it out I guess.
///
/// Fires: Matchmaker.Initiated
/// </summary>
public static void Initiate() public static void Initiate()
{ {
MakerTimer = new Timer(); MakerTimer = new Timer();
@ -53,6 +86,12 @@ public static void Initiate()
} }
} }
/// <summary>
/// Matchmake in the supplied lobby.
///
/// Fires: MorePlayersFound upon player leave/join.
/// </summary>
/// <param name="si">The server to matchmake in.</param>
public static void Matchmake(ServerInfo si) public static void Matchmake(ServerInfo si)
{ {
SelectedServer = si; SelectedServer = si;
@ -88,8 +127,12 @@ public static void Matchmake(ServerInfo si)
{ {
SelectedNetwork = Players[index]; SelectedNetwork = Players[index];
MakerTimer.Stop(); MakerTimer.Stop();
PlayerListener = new NetListener(si, SelectedNetwork);
SecondaryTransmitter = new NetTransmitter(si, API.CurrentSave.MyOnlineNetwork);
SelectedNetworkListener = new NetListener(si, API.CurrentSave.MyOnlineNetwork); SelectedNetworkListener = new NetListener(si, API.CurrentSave.MyOnlineNetwork);
SelectedNetworkTransmitter = new NetTransmitter(si, SelectedNetwork); SelectedNetworkTransmitter = new NetTransmitter(si, SelectedNetwork);
var h = new HackUI(SelectedNetworkTransmitter, SelectedNetworkListener, PlayerListener, SecondaryTransmitter);
h.Show();
Package_Grabber.SendMessage(SelectedServer.IPAddress, $"leave_lobby {JsonConvert.SerializeObject(API.CurrentSave.MyOnlineNetwork)}"); Package_Grabber.SendMessage(SelectedServer.IPAddress, $"leave_lobby {JsonConvert.SerializeObject(API.CurrentSave.MyOnlineNetwork)}");
} }
else else
@ -106,14 +149,42 @@ public static void Matchmake(ServerInfo si)
} }
/// <summary>
/// Fired when Initiate() finishes.
/// </summary>
public static event EventHandler Initiated; public static event EventHandler Initiated;
/// <summary>
/// Fired when someone enters/exits a lobby that we are in.
/// </summary>
public static event EventHandler MorePlayersFound; public static event EventHandler MorePlayersFound;
/// <summary>
/// Helper method to allow me to invoke some code on the ShiftOS desktop thread (for UI access)
/// </summary>
/// <param name="method">The code to invoke (use a lambda expression or just pump a void through.)</param>
public static void invoke(Action method) public static void invoke(Action method)
{ {
API.CurrentSession.Invoke(method); API.CurrentSession.Invoke(method);
} }
internal static void DestroySession()
{
Servers.Clear();
Players.Clear();
ClearEvents();
SelectedNetwork = null;
SelectedNetworkTransmitter = null;
SelectedNetworkListener = null;
PlayerListener = null;
//Good to go, I guess.
}
public static void ClearEvents()
{
Initiated = null;
MorePlayersFound = null;
}
} }
public class NetListener public class NetListener
@ -181,6 +252,11 @@ private void register_events(ServerInfo si, Network net)
ModuleUpgraded?.Invoke(this, new Events.ModuleUpgraded { hostname = hostnametoupgrade, grade = newgrade }); ModuleUpgraded?.Invoke(this, new Events.ModuleUpgraded { hostname = hostnametoupgrade, grade = newgrade });
}); });
break; break;
case "finish":
string json = data.Command.Remove(0, 7);
var winner = JsonConvert.DeserializeObject<Network>(json);
Won?.Invoke(this, new Events.Won(winner));
break;
case "disable": case "disable":
invoke(() => invoke(() =>
{ {
@ -205,6 +281,7 @@ public void invoke(Action method)
public event EventHandler<Events.ModuleRemoved> ModuleRemoved; public event EventHandler<Events.ModuleRemoved> ModuleRemoved;
public event EventHandler<Events.ModuleUpgraded> ModuleUpgraded; public event EventHandler<Events.ModuleUpgraded> ModuleUpgraded;
public event EventHandler<Events.Disabled> ModuleDisabled; public event EventHandler<Events.Disabled> ModuleDisabled;
public event EventHandler<Events.Won> Won;
} }
public class NetTransmitter public class NetTransmitter
@ -216,8 +293,6 @@ public NetTransmitter(ServerInfo si, Network enemy)
{ {
EnemyIdent = enemy; EnemyIdent = enemy;
serverInfo = si; serverInfo = si;
var h = new HackUI(this, Matchmaker.SelectedNetworkListener);
h.Show();
//HackUI will handle everything else to do with our network. //HackUI will handle everything else to do with our network.
} }
@ -241,6 +316,10 @@ public void send_message(Messages msg, object value)
string healthsetstr = value as string; string healthsetstr = value as string;
Package_Grabber.SendMessage(serverInfo.IPAddress, $"set_health {healthsetstr}", EnemyIdent); Package_Grabber.SendMessage(serverInfo.IPAddress, $"set_health {healthsetstr}", EnemyIdent);
break; break;
case Messages.FinishBattle:
string json = JsonConvert.SerializeObject(value as Network);
Package_Grabber.SendMessage(serverInfo.IPAddress, $"finish {json}");
break;
case Messages.Disabled: case Messages.Disabled:
string hnamestr = value as string; string hnamestr = value as string;
Package_Grabber.SendMessage(serverInfo.IPAddress, $"disable {hnamestr}", EnemyIdent); Package_Grabber.SendMessage(serverInfo.IPAddress, $"disable {hnamestr}", EnemyIdent);
@ -255,6 +334,7 @@ public enum Messages
RemoveModule, RemoveModule,
SetHealth, SetHealth,
Disabled, Disabled,
FinishBattle,
} }
} }
@ -271,6 +351,16 @@ public class Disabled : EventArgs
public string hostName { get; set; } public string hostName { get; set; }
} }
public class Won : EventArgs
{
public Network Winner { get; private set; }
public Won(Network winner)
{
Winner = winner;
}
}
public class ModulePlaced : EventArgs public class ModulePlaced : EventArgs
{ {
public Module new_module { get; set; } public Module new_module { get; set; }

View file

@ -4,5 +4,6 @@
"Orange Inc.":{"IsLeader":false,"Name":"Orange Inc.","FriendDesc":"Orange is a computer industry giant. Let's see how they can compete in a Hacker Battle.","Description":"Orange is a computer industry giant, creators of the Tangerine Operating System, and various portable devices like the TangerineBook, OrangePad, and OrangePhone.","FriendSpeed":100,"FriendSkill":200,"Difficulty":"hard","Network":[{"Hostname":"orange_inc.","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"blood_orange","ModuleType":0,"Type":6,"HP":0,"Grade":4,"X":484,"Y":203},{"Hostname":"yummy","ModuleType":0,"Type":2,"HP":0,"Grade":3,"X":497,"Y":150},{"Hostname":"juicy","ModuleType":0,"Type":5,"HP":0,"Grade":4,"X":356,"Y":212},{"Hostname":"sweet","ModuleType":0,"Type":9,"HP":0,"Grade":2,"X":316,"Y":212},{"Hostname":"orange","ModuleType":0,"Type":2,"HP":0,"Grade":3,"X":490,"Y":270}]}, "Orange Inc.":{"IsLeader":false,"Name":"Orange Inc.","FriendDesc":"Orange is a computer industry giant. Let's see how they can compete in a Hacker Battle.","Description":"Orange is a computer industry giant, creators of the Tangerine Operating System, and various portable devices like the TangerineBook, OrangePad, and OrangePhone.","FriendSpeed":100,"FriendSkill":200,"Difficulty":"hard","Network":[{"Hostname":"orange_inc.","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"blood_orange","ModuleType":0,"Type":6,"HP":0,"Grade":4,"X":484,"Y":203},{"Hostname":"yummy","ModuleType":0,"Type":2,"HP":0,"Grade":3,"X":497,"Y":150},{"Hostname":"juicy","ModuleType":0,"Type":5,"HP":0,"Grade":4,"X":356,"Y":212},{"Hostname":"sweet","ModuleType":0,"Type":9,"HP":0,"Grade":2,"X":316,"Y":212},{"Hostname":"orange","ModuleType":0,"Type":2,"HP":0,"Grade":3,"X":490,"Y":270}]},
"UltraDOS Foundation":{"IsLeader":false,"Name":"UltraDOS Foundation","FriendDesc":"UltraDOS Foundation is a group of competent programmers responsible for the ShiftOS-based UltraDOS operating system.","Description":"UltraDOS Foundation is a group of competent programmers responsible for the ShiftOS-based UltraDOS operating system.","FriendSpeed":65,"FriendSkill":75,"Difficulty":"medium","Network":[{"Hostname":"ultrados_foundation","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"ud_trt1","ModuleType":0,"Type":3,"HP":0,"Grade":2,"X":361,"Y":171},{"Hostname":"ud_trt2","ModuleType":0,"Type":3,"HP":0,"Grade":2,"X":358,"Y":220},{"Hostname":"ud_repairer","ModuleType":0,"Type":9,"HP":0,"Grade":4,"X":494,"Y":215}]}, "UltraDOS Foundation":{"IsLeader":false,"Name":"UltraDOS Foundation","FriendDesc":"UltraDOS Foundation is a group of competent programmers responsible for the ShiftOS-based UltraDOS operating system.","Description":"UltraDOS Foundation is a group of competent programmers responsible for the ShiftOS-based UltraDOS operating system.","FriendSpeed":65,"FriendSkill":75,"Difficulty":"medium","Network":[{"Hostname":"ultrados_foundation","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"ud_trt1","ModuleType":0,"Type":3,"HP":0,"Grade":2,"X":361,"Y":171},{"Hostname":"ud_trt2","ModuleType":0,"Type":3,"HP":0,"Grade":2,"X":358,"Y":220},{"Hostname":"ud_repairer","ModuleType":0,"Type":9,"HP":0,"Grade":4,"X":494,"Y":215}]},
"LadouceurNet":{"IsLeader":false,"Name":"LadouceurNet","FriendDesc":"The LadouceurNet - the Shiftnet that never happened.","Description":"The LadouceurNet - the Shiftnet that never happened.","FriendSpeed":140,"FriendSkill":125,"Difficulty":"medium","Network":[{"Hostname":"ladouceurnet","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"mod4","ModuleType":0,"Type":3,"HP":0,"Grade":4,"X":479,"Y":127},{"Hostname":"mod5","ModuleType":0,"Type":3,"HP":0,"Grade":4,"X":382,"Y":131},{"Hostname":"mod5_1","ModuleType":0,"Type":1,"HP":0,"Grade":2,"X":482,"Y":302},{"Hostname":"mod5_2","ModuleType":0,"Type":9,"HP":0,"Grade":4,"X":359,"Y":304},{"Hostname":"mod8","ModuleType":0,"Type":5,"HP":0,"Grade":4,"X":403,"Y":336},{"Hostname":"mod1","ModuleType":0,"Type":3,"HP":0,"Grade":4,"X":444,"Y":335}]}, "LadouceurNet":{"IsLeader":false,"Name":"LadouceurNet","FriendDesc":"The LadouceurNet - the Shiftnet that never happened.","Description":"The LadouceurNet - the Shiftnet that never happened.","FriendSpeed":140,"FriendSkill":125,"Difficulty":"medium","Network":[{"Hostname":"ladouceurnet","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"mod4","ModuleType":0,"Type":3,"HP":0,"Grade":4,"X":479,"Y":127},{"Hostname":"mod5","ModuleType":0,"Type":3,"HP":0,"Grade":4,"X":382,"Y":131},{"Hostname":"mod5_1","ModuleType":0,"Type":1,"HP":0,"Grade":2,"X":482,"Y":302},{"Hostname":"mod5_2","ModuleType":0,"Type":9,"HP":0,"Grade":4,"X":359,"Y":304},{"Hostname":"mod8","ModuleType":0,"Type":5,"HP":0,"Grade":4,"X":403,"Y":336},{"Hostname":"mod1","ModuleType":0,"Type":3,"HP":0,"Grade":4,"X":444,"Y":335}]},
"GimmeX":{"IsLeader":false,"Name":"GimmeX","FriendDesc":"The data stealer","Description":"The data stealer","FriendSpeed":10,"FriendSkill":15,"Difficulty":"medium","Network":[{"Hostname":"gimmex","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"Repairman","ModuleType":0,"Type":9,"HP":0,"Grade":1,"X":442,"Y":145},{"Hostname":"Stealer","ModuleType":0,"Type":8,"HP":0,"Grade":4,"X":371,"Y":121},{"Hostname":"Attacker","ModuleType":0,"Type":3,"HP":0,"Grade":1,"X":359,"Y":218},{"Hostname":"Defense","ModuleType":0,"Type":5,"HP":0,"Grade":4,"X":494,"Y":227},{"Hostname":"IGiveBreaks","ModuleType":0,"Type":6,"HP":0,"Grade":1,"X":522,"Y":156}]} "GimmeX":{"IsLeader":false,"Name":"GimmeX","FriendDesc":"The data stealer","Description":"The data stealer","FriendSpeed":10,"FriendSkill":15,"Difficulty":"medium","Network":[{"Hostname":"gimmex","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"Repairman","ModuleType":0,"Type":9,"HP":0,"Grade":1,"X":442,"Y":145},{"Hostname":"Stealer","ModuleType":0,"Type":8,"HP":0,"Grade":4,"X":371,"Y":121},{"Hostname":"Attacker","ModuleType":0,"Type":3,"HP":0,"Grade":1,"X":359,"Y":218},{"Hostname":"Defense","ModuleType":0,"Type":5,"HP":0,"Grade":4,"X":494,"Y":227},{"Hostname":"IGiveBreaks","ModuleType":0,"Type":6,"HP":0,"Grade":1,"X":522,"Y":156}]},
"A-Labs":{"IsLeader":false,"Name":"A-Labs","FriendDesc":"A-Labs is a group that is attempting to find a cure for cancer.","Description":"A-Labs is a group that is attempting to find a cure for cancer.","FriendSpeed":9999,"FriendSkill":9999,"Difficulty":"hard","Network":[{"Hostname":"a-labs","ModuleType":0,"Type":0,"HP":100,"Grade":1,"X":0,"Y":0},{"Hostname":"Subject_1","ModuleType":0,"Type":1,"HP":0,"Grade":2,"X":512,"Y":211},{"Hostname":"Subject_2","ModuleType":0,"Type":7,"HP":0,"Grade":4,"X":330,"Y":194},{"Hostname":"Subject_3","ModuleType":0,"Type":9,"HP":0,"Grade":4,"X":427,"Y":270},{"Hostname":"Subject_4","ModuleType":0,"Type":9,"HP":0,"Grade":4,"X":437,"Y":155},{"Hostname":"Subject_5","ModuleType":0,"Type":2,"HP":0,"Grade":3,"X":284,"Y":204}]}
} }