mirror of
https://github.com/seriocomedy/ShiftOS-C-.git
synced 2025-01-22 10:50:27 -05:00
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:
parent
dec9dfac5e
commit
20fcece26d
4 changed files with 230 additions and 34 deletions
|
@ -168,6 +168,16 @@ public void LaunchAttack(AttackType type, int rate)
|
|||
|
||||
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 void Deteriorate(int amount)
|
||||
|
|
|
@ -24,7 +24,9 @@ public HackUI()
|
|||
|
||||
private bool InOnlineBattle = false;
|
||||
private Online.Hacking.NetTransmitter transmitter = null;
|
||||
private Online.Hacking.NetTransmitter _playerTransmitter = null;
|
||||
private Online.Hacking.NetListener receiver = null;
|
||||
private Online.Hacking.NetListener player_listener = null;
|
||||
|
||||
public HackUI(EnemyHacker enemy)
|
||||
{
|
||||
|
@ -32,11 +34,13 @@ public HackUI(EnemyHacker enemy)
|
|||
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;
|
||||
transmitter = t;
|
||||
receiver = l;
|
||||
player_listener = playerListener;
|
||||
_playerTransmitter = playerTransmitter;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
@ -74,6 +78,8 @@ private void LoadPlayerScreen()
|
|||
{
|
||||
SetupTutorialUI(0);
|
||||
}
|
||||
if (InOnlineBattle)
|
||||
LoadOnlinePlayer();
|
||||
}
|
||||
|
||||
private void VisualizePlayerNetwork()
|
||||
|
@ -102,6 +108,59 @@ private void VisualizePlayerNetwork()
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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.Show();
|
||||
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.Show();
|
||||
c.Flash(lbenemycompromised);
|
||||
_playerTransmitter?.send_message(Online.Hacking.NetTransmitter.Messages.SetHealth, $"{c.Hostname} {c.HP}");
|
||||
}
|
||||
|
||||
private decimal TotalEnemyHP = 0;
|
||||
|
@ -1390,44 +1450,46 @@ private void tmrenemyhealthdetect_Tick(object sender, EventArgs e)
|
|||
lbcodepoints.Text = $"Codepoints: {API.Codepoints}";
|
||||
var rnd = new Random();
|
||||
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.Enslaved)
|
||||
elist = AllEnemyComputers;
|
||||
else
|
||||
elist = AllPlayerComputers;
|
||||
foreach (var enemy in elist)
|
||||
if (pc.Disabled == false)
|
||||
{
|
||||
chance = rnd.Next(1, 20);
|
||||
if (chance == 10)
|
||||
var elist = new List<Computer>();
|
||||
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());
|
||||
}
|
||||
else if (TutorialProgress == 32)
|
||||
{
|
||||
enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
|
||||
if (TutorialProgress == 9)
|
||||
{
|
||||
ThisPlayerPC.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
|
||||
}
|
||||
else if (TutorialProgress == 32)
|
||||
{
|
||||
enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
|
||||
}
|
||||
else
|
||||
{
|
||||
enemy.Enemies.Clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
enemy.Enemies.Clear();
|
||||
enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
enemy.LaunchAttack(pc.GetProperType(), pc.GetDamageRate());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pc in AllEnemyComputers)
|
||||
{
|
||||
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)
|
||||
|
@ -1623,7 +1709,16 @@ private void Receiver_ModuleHealthSet(object sender, Online.Hacking.Events.Healt
|
|||
if (m.Hostname == e.host_name)
|
||||
mod = m;
|
||||
}
|
||||
int old_hp = mod.HP;
|
||||
mod.HP = e.health;
|
||||
if(mod.HP > old_hp)
|
||||
{
|
||||
mod.throw_repaired();
|
||||
}
|
||||
else
|
||||
{
|
||||
mod.throw_damaged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -10,16 +24,35 @@ namespace ShiftOS.Online.Hacking
|
|||
{
|
||||
public class Matchmaker
|
||||
{
|
||||
//All available lobbies.
|
||||
public static List<ServerInfo> Servers = null;
|
||||
|
||||
//All players in the lobby.
|
||||
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 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;
|
||||
|
||||
/// <summary>
|
||||
/// This either starts matchmaking or grabs server info. Try it out I guess.
|
||||
///
|
||||
/// Fires: Matchmaker.Initiated
|
||||
/// </summary>
|
||||
public static void Initiate()
|
||||
{
|
||||
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)
|
||||
{
|
||||
SelectedServer = si;
|
||||
|
@ -88,8 +127,12 @@ public static void Matchmake(ServerInfo si)
|
|||
{
|
||||
SelectedNetwork = Players[index];
|
||||
MakerTimer.Stop();
|
||||
PlayerListener = new NetListener(si, SelectedNetwork);
|
||||
SecondaryTransmitter = new NetTransmitter(si, API.CurrentSave.MyOnlineNetwork);
|
||||
SelectedNetworkListener = new NetListener(si, API.CurrentSave.MyOnlineNetwork);
|
||||
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)}");
|
||||
}
|
||||
else
|
||||
|
@ -106,14 +149,42 @@ public static void Matchmake(ServerInfo si)
|
|||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fired when Initiate() finishes.
|
||||
/// </summary>
|
||||
public static event EventHandler Initiated;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when someone enters/exits a lobby that we are in.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
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
|
||||
|
@ -181,6 +252,11 @@ private void register_events(ServerInfo si, Network net)
|
|||
ModuleUpgraded?.Invoke(this, new Events.ModuleUpgraded { hostname = hostnametoupgrade, grade = newgrade });
|
||||
});
|
||||
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":
|
||||
invoke(() =>
|
||||
{
|
||||
|
@ -205,6 +281,7 @@ public void invoke(Action method)
|
|||
public event EventHandler<Events.ModuleRemoved> ModuleRemoved;
|
||||
public event EventHandler<Events.ModuleUpgraded> ModuleUpgraded;
|
||||
public event EventHandler<Events.Disabled> ModuleDisabled;
|
||||
public event EventHandler<Events.Won> Won;
|
||||
}
|
||||
|
||||
public class NetTransmitter
|
||||
|
@ -216,8 +293,6 @@ public NetTransmitter(ServerInfo si, Network enemy)
|
|||
{
|
||||
EnemyIdent = enemy;
|
||||
serverInfo = si;
|
||||
var h = new HackUI(this, Matchmaker.SelectedNetworkListener);
|
||||
h.Show();
|
||||
//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;
|
||||
Package_Grabber.SendMessage(serverInfo.IPAddress, $"set_health {healthsetstr}", EnemyIdent);
|
||||
break;
|
||||
case Messages.FinishBattle:
|
||||
string json = JsonConvert.SerializeObject(value as Network);
|
||||
Package_Grabber.SendMessage(serverInfo.IPAddress, $"finish {json}");
|
||||
break;
|
||||
case Messages.Disabled:
|
||||
string hnamestr = value as string;
|
||||
Package_Grabber.SendMessage(serverInfo.IPAddress, $"disable {hnamestr}", EnemyIdent);
|
||||
|
@ -255,6 +334,7 @@ public enum Messages
|
|||
RemoveModule,
|
||||
SetHealth,
|
||||
Disabled,
|
||||
FinishBattle,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,6 +351,16 @@ public class Disabled : EventArgs
|
|||
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 Module new_module { get; set; }
|
||||
|
|
|
@ -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}]},
|
||||
"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}]},
|
||||
"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}]}
|
||||
}
|
Loading…
Reference in a new issue