Introducing Skynet – A Minecraft Bot

Skynet is a Minecraft bot written with Node.js, MySQL, and Symfony2 for a Minecraft server called Civcraft. You can see it on Github:  https://github.com/JonnyD/Skynet/blob/v1.0.3/lib/skynet-minecraft.js

(Keep in mind it was my first time coding with Node.js so it may be a bit cringey but I did my best to make the code readable by using Async and other modules.

Civcraft is Minecraft server with the goal of leaving players as free as possible to generate their own political, social, and economic order within Minecraft through the use of several custom mods as well as some more general ones. That means no rulers but doesn’t mean no rules. Those rules are enforced by other players in the community. It’s part of a social experiment to see which ideas would win in a free society.

This bot was for my in-game security business that allows players, in combination with other plugins, to make their property more secure – to find out which players are using alts (alternative accounts), which players are trespassing on their property or stealing their property, which criminals are in the vicinity so you can avoid that area all without logging in.

var mineflayer = require('mineflayer');
var mysql = require('mysql');
var moment = require('moment');
var async = require('async');
var config = require('./config')

var connection = mysql.createConnection({
  host: config.mysql.host,
  user: config.mysql.user,
  password: config.mysql.password,
  database: config.mysql.database
});
connection.connect();

var bot;
var options = {
  host: config.mc.host,
  port: config.mc.port,
  username: config.mc.username,
  password: config.mc.password
};
startConnectionTimeout();

process.on('uncaughtException', function(exception) {
  console.log("caught exception " + exception);
});

var connected = false;
var connectionTimeout;
var afkTimeout;
var afk1MinuteTimeout;
var afk5MinutesTimeout;
var afk10MinutesTimeout;

function connect() {
  console.log("[" + getTimestamp() + "] Attempting to login");
  bot = mineflayer.createBot(options);
  bot.on('connect', function() {
    bindEvents(bot);
  });
}

function bindEvents(bot) {
  console.log("[" + getTimestamp() + "] Binding Events");

  bot.on('login', function() {
    console.log("[" + getTimestamp() + "] I logged in.");
    connected = true;
    stopConnectionTimeout();
  });

  bot.on('playerJoined', function(player) {
    loginPlayer(player);
  });

  bot.on('playerLeft', function(player) {
    logoutPlayer(player);
  });

  bot.on('whisper', function(username, message, rawMessage) {
    console.log("message", message, "rawMessage", rawMessage, "username", username);
    if (username === config.settings.owner) {
      if (message === " quit" || message === " restart") {
        connected = false;
        var timestamp = getTimestamp();
        clearTimeouts();
        async.series([
          function(callback) {
            bot.quit();
            console.log("logging out all players");
            setTimeout(function () {
              logoutAllPlayers(timestamp, function(finished) {
                console.log("logged out all players " + finished);
                callback();
              });
            }, 30 * 1000);
          },
          function(callback) {
            if (message === " restart") {
              callback();
            }
          }
        ]);
      }
    }
  });

  bot.on('chat', function(username, message) {
    console.log("chat " + username + " " + message);
  });

  bot.on('nonSpokenChat', function(message) {
    console.log("nonSpokenChat " + message);
    if (message.indexOf('AFK Plugin') >= 0) {
      if (message.indexOf('10 seconds') >= 0) {
        bot.chat(config.settings.antiAfkMessage);
      } else if (message.indexOf('1 minute') >= 0) {
        afk1MinuteTimeout = setTimeout(function () {
          bot.chat(config.settings.antiAfkMessage);
        }, 30 * 1000);
      } else if (message.indexOf('5 minutes') >= 0) {
        afk5MinutesTimeout = setTimeout(function () {
          bot.chat(config.settings.antiAfkMessage);
        }, 60 * 1000);
      } else if (message.indexOf('10 minutes') >= 0) {
        afk10MinutesTimeout = setTimeout(function () {
          bot.chat(config.settings.antiAfkMessage);
        }, 120 * 1000);
      } else {
        bot.chat(config.settings.antiAfkMessage);
      }
    }
  });

  bot.on('kicked', function(reason) {
    connected = false;
    stopConnectionTimeout();
    var timestamp = getTimestamp();
    console.log("[" + timestamp + "] I got kicked for", reason, "lol");
    clearTimeouts();
    async.series([
      function(callback) {
        console.log("logging out all players");
        setTimeout(function () {
          logoutAllPlayers(timestamp, function(finished) {
            console.log("logged out all players " + finished);
            callback();
          });
        }, 30 * 1000);
      },
      function(callback) {
        startConnectionTimeout();
        callback();
      }
    ]);
  });

  bot.on('spawn', function() {
    console.log("[" + getTimestamp() + "] I spawned");
    startAfkTimeout();
  });

  bot.on('death', function() {
    console.log("[" + getTimestamp() + "] I died x.x.");
  });
}

function getPlayer(username, timestamp, callback) {
  findPlayer(username, function(playerId) {
    if (typeof playerId === 'undefined') {
      createPlayer(username, timestamp, function(playerId) {
        callback(playerId);
      });
    } else {
      callback(playerId);
    }
  });
}

function addEvent(playerId, type, timestamp, callback) {
  var event = {player_id: playerId, event_type_id: type, timestamp: timestamp};
  connection.query('INSERT INTO event SET ?', event, function(err, result) {
    callback(result.insertId);
  });
}

function findEventTimestamp(eventId, callback) {
  connection.query('SELECT timestamp AS timestamp FROM event WHERE id = ' + eventId, function(err, rows, fields) {
    if (rows.length > 0) {
      callback(rows[0].timestamp);
    }
  });
}

function addSession(username, playerId, timestamp, loginEventId, callback) {
  var session = { player_id: playerId, login: loginEventId, login_timestamp: timestamp };
  connection.query('INSERT INTO session SET ?', session, function(err, result) {
    logVerbose("[" + getTimestamp() + "] Attempting to start session for " + username + " with eventId " + loginEventId);
    callback(result.insertId);
  });
}

function updateSession(sessionId, logoutEventId, logoutTimestamp, duration, callback) {
  connection.query("UPDATE session SET logout = ?, duration = ?, logout_timestamp = ? WHERE id = ?", [logoutEventId, duration, logoutTimestamp, sessionId], function(err, result) {
    callback(1);
  });
}

function findSession(playerId, callback) {
  connection.query('SELECT id AS session_id, login AS loginEvent FROM session WHERE player_id = ? AND logout IS NULL ORDER BY login_timestamp ASC LIMIT 1', [playerId], function(err, rows, fields) {
    if (rows.length > 0) {
      callback(rows[0].session_id, rows[0].loginEvent);
    }
  });
}

function findPlayer(username, callback) {
  connection.query('SELECT id from player where username = ?', [username], function(err, rows, fields) {
    var playerId;
    if (rows.length > 0) {
      playerId = rows[0].id;
    }
    callback(playerId);
  });
}

function findOnlinePlayers(callback) {
  connection.query('SELECT * FROM session s, player p WHERE p.id = s.player_id AND logout IS NULL', function(error, rows, fields) {
    if (rows.length > 0) {
      callback(rows);
    }
  });
}

function logoutAllPlayers(timestamp, callback) {
  var counter = 0;
  findOnlinePlayers(function(sessions) {
    logVerbose("[" + timestamp + "] Sessions found: " + sessions.length);
    if (sessions.length > 0) {
      sessions.forEach(function(session) {
        var playerId = session.player_id;
        var username = session.username;

        async.waterfall([
          function(callback) {
            addEvent(playerId, 2, timestamp, function(logoutEventId) {
              logVerbose("[" + timestamp + "] " + "Created logout: " + logoutEventId + " for " + username + " (" + playerId +")");
              callback(null, logoutEventId);
            });
          },
          function(logoutEventId, callback) {
            findSession(playerId, function(sessionId, loginEventId) {
              logVerbose("[" + timestamp + "] " + "Found session: " + sessionId +" for " + username + " (" + playerId +")");
              callback(null, sessionId, loginEventId, logoutEventId);
            });
          },
          function(sessionId, loginEventId, logoutEventId, callback) {
            findEventTimestamp(loginEventId, function(loginTimestamp) {
              findEventTimestamp(logoutEventId, function(logoutTimestamp) {
                var difference = diffBetweenTimestamps(loginTimestamp, logoutTimestamp);
                logVerbose("[" + timestamp + "] " + "Duration: " + difference + " for " + username);
                callback(null, sessionId, logoutEventId, difference);
              });
            });
          },
          function(sessionId, logoutEventId, difference, callback) {
            updateSession(sessionId, logoutEventId, timestamp, difference, function(finished) {
              logVerbose("[" + timestamp + "] Ended session: " + sessionId + " for " + username + " (" + playerId + ")");
              callback(null, finished);
            });1
          }
        ], function(err, result) {
          counter = counter + 1;
          if (counter === sessions.length) {
            callback(1);
          }
        });
      });
    }
  });
}

function createPlayer(username, timestamp, callback) {
  var newPlayer  = {username: username, timestamp: timestamp};
  connection.query('INSERT INTO player SET ?', newPlayer, function(err, result) {
    var playerId;
    playerId = result.insertId;
    logVerbose("[" + timestamp + "] " + "Created player: " + username + " (" + playerId + ")");
    callback(playerId);
  });
}

function loginPlayer(player) {
  var timestamp = getTimestamp();
  console.log("[" + timestamp + "] " + player.username + " joined");
  var username = player.username;
  async.waterfall([
    function(callback) {
      getPlayer(username, timestamp, function(playerId) {
        callback(null, playerId);
      });
    },
    function(playerId, callback) {
      addEvent(playerId, 1, timestamp, function(eventId) {
        logVerbose("[" + timestamp + "] " + "Created login: " + eventId + " for " + player.username + " (" + playerId +")");
        callback(null, playerId, eventId);
      });
    },
    function(playerId, eventId, callback) {
      addSession(player.username, playerId, timestamp, eventId, function(sessionId) {
        logVerbose("[" + timestamp + "] Started session: " + sessionId + " for " + player.username + " (" + playerId +")");
        callback(null, playerId, sessionId);
      });
    }
  ]);
}

function logoutPlayer(player) {
  var timestamp = getTimestamp();
  console.log("[" + timestamp + "] " + player.username + " left");

  var username = player.username;
  async.waterfall([
    function(callback) {
      findPlayer(username, function(playerId) {
        callback(null, playerId);
      });
    },
    function(playerId, callback) {
      addEvent(playerId, 2, timestamp, function(logoutEventId) {
        logVerbose("[" + timestamp + "] " + "Created logout: " + logoutEventId + " for " + username + " (" + playerId +")");
        callback(null, playerId, logoutEventId);
      });
    },
    function(playerId, logoutEventId, callback) {
      findSession(playerId, function(sessionId, loginEventId) {
        logVerbose("[" + timestamp + "] " + "Found session: " + sessionId +" for " + username + " (" + playerId +")");
        callback(null, playerId, sessionId, loginEventId, logoutEventId);
      });
    },
    function(playerId, sessionId, loginEventId, logoutEventId, callback) {
      findEventTimestamp(loginEventId, function(loginTimestamp) {
        findEventTimestamp(logoutEventId, function(logoutTimestamp) {
          var difference = diffBetweenTimestamps(loginTimestamp, logoutTimestamp);
          logVerbose("[" + timestamp + "] " + "Duration: " + difference + " for " + username);
          callback(null, playerId, sessionId, logoutEventId, difference);
        });
      });
    },
    function(playerId, sessionId, logoutEventId, difference, callback) {
      updateSession(sessionId, logoutEventId, timestamp, difference, function(updated) {
        logVerbose("[" + timestamp + "] " + "Ended session: " + sessionId + " for " + username + " (" + playerId +")");
      });
    }
  ]);
}

function getTimestamp() {
  var MyDate = new Date();
  var MyDateString;
  MyDateString = MyDate.getFullYear() + "-"
    + ('0' + (MyDate.getMonth()+1)).slice(-2) + "-"
    + ('0' + MyDate.getDate()).slice(-2) + " "
    + ('0' + MyDate.getHours()).slice(-2) + ":"
    + ('0' + MyDate.getMinutes()).slice(-2) + ":"
    + ('0' + MyDate.getSeconds()).slice(-2);
  return MyDateString;
}

function parseTimestamp(input) {
  var timestamp = input.split(" ");
  var date = timestamp[0];
  var time = timestamp[1];
  var dateParts = date.split("-");
  var timeParts = time.split(":");
  // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  return moment([dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1], timeParts[2]]);
}

function diffBetweenTimestamps(timestamp1, timestamp2) {
  var timestamp1Parsed = moment(timestamp1);
  var timestamp2Parsed = moment(timestamp2);
  var difference = timestamp2Parsed.diff(timestamp1Parsed, 'seconds');
  return difference;
}

function clearTimeouts() {
  stopAfkTimeout();
  clearTimeout(afk1MinuteTimeout);
  clearTimeout(afk5MinutesTimeout);
  clearTimeout(afk10MinutesTimeout);
}

var startAfk;
var nextAtAfk;
function startAfkTimeout() {
  if (!startAfk) {
    startAfk = new Date().getTime();
    nextAtAfk = startAfk;
  }
  nextAtAfk += 30 * 1000;

  if (connected) {
    bot.setControlState('jump', true);
    bot.setControlState('jump', false);
    console.log("[" + getTimestamp() + "] I jumped");

    afkTimeout = setTimeout(startAfkTimeout, nextAtAfk - new Date().getTime());
  }
}

function stopAfkTimeout() {
  startAfk = null;
  nextAtAfk = null;
  clearTimeout(afkTimeout);
}

var startConnection;
var nextAtConnection;
function startConnectionTimeout() {
  if (!startConnection) {
    startConnection = new Date().getTime();
    nextAtConnection = startConnection;
  }
  nextAtConnection += 10 * 1000;

  if (!connected) {
    connect();

    afkTimeout = setTimeout(startConnectionTimeout, nextAtConnection - new Date().getTime());
  }
}

function stopConnectionTimeout() {
  startConnection = null;
  nextAtConnection = null;
  clearTimeout(connectionTimeout);
}

function logVerbose(message) {
  if (config.settings.verboseLogging) {
    console.log(message);
  }
}
 

Calculating Standings by Results

Using a PHP Framework called Symfony2, I am developing a web application for people who play the video game Fifa to compete against each other in an online competition. It’s on Github: https://github.com/JonnyD/Elite-Fifa-Leagues

In the picture above you can see the UI of the league standings. These values are calculated and stored in a Standings table after every confirmed match. There’s a problem with this and that is storing calculated values breaks normalization. The only time this could be acceptable is in cases where you want to improve performance by not having to re-calculate the values every time you need them.

However, what if I want to find out a teams standing by their Last X Games Played at Home, Last X Games Played Away, or Last X Games Played Combined?

Here’s how I could get Standings by home matches only:

SELECT team.name, home_team_id AS team_id,
    COUNT(*) AS played,
    SUM((CASE WHEN home_score > away_score THEN 1 ELSE 0 END)) AS won,
    SUM((CASE WHEN away_score > home_score THEN 1 ELSE 0 END)) AS lost,
    SUM((CASE WHEN home_score = away_score THEN 1 ELSE 0 END)) AS drawn,
    SUM(home_score) AS goalsFor,
    SUM(away_score) AS goalsAgainst,
    SUM(home_score - away_score) AS goalDifference,
    SUM((CASE WHEN home_score > away_score THEN 3 WHEN home_score = away_score THEN 1 ELSE 0 END)) AS points
FROM matches
INNER JOIN team ON matches.home_team_id = team.id
WHERE league_id = 94
    AND season_id = 82
    AND confirmed IS NOT NULL
GROUP BY home_team_id
ORDER BY POINTS DESC;

Here’s how I could get Standings by Away matches only:

```
SELECT team.name, away_team_id AS team_id,
    COUNT(*) AS played,
    SUM((CASE WHEN away_score > home_score THEN 1 ELSE 0 END)) AS won,
    SUM((CASE WHEN home_score > away_score THEN 1 ELSE 0 END)) AS lost,
    SUM((CASE WHEN home_score = away_score THEN 1 ELSE 0 END)) as drawn,
    SUM(away_score) AS goalsFor,
    SUM(home_score) AS goalsAgainst,
    SUM(away_score - home_score) AS goalDifference,
    SUM((CASE WHEN away_score > home_score THEN 3 WHEN away_score = home_score THEN 1 ELSE 0 END)) AS points
FROM matches
INNER JOIN team ON matches.away_team_id = team.id
WHERE league_id = 94
    AND season_id = 82
    AND confirmed IS NOT NULL
GROUP BY away_team_id
ORDER BY points DESC;

Here’s how I could get Standings by Home and Away matches combined:

SELECT team.name,
       team_id AS team_id,
       COUNT(*) AS played,
       SUM((CASE WHEN team_score > other_team_score THEN 1 ELSE 0 END)) AS won,
       SUM((CASE WHEN team_score < other_team_score THEN 1 ELSE 0 END)) AS lost,
       SUM((CASE WHEN team_score = other_team_score THEN 1 ELSE 0 END)) AS drawn,
       SUM(team_score) AS goalsFor,
       SUM(other_team_score) AS goalsAgainst,
       SUM(team_score - other_team_score) AS goalDifference,
       SUM((CASE WHEN team_score > other_team_score THEN 3
                 WHEN team_score = other_team_score THEN 1
                 ELSE 0 END)) AS points
FROM
    (
        -- LIST TEAM STATS WHEN PLAYED AS HOME_TEAM
        SELECT
             id,
             league_id,
             season_id,
             home_team_id as team_id,
             home_score   as team_score,
             away_score   as other_team_score,
             confirmed
        FROM    matches
        UNION ALL
        -- LIST TEAM STATS WHEN PLAYED AS AWAY_TEAM
        SELECT
             id,
             league_id,
             season_id,
             away_team_id as team_id,
             away_score   as team_score,
             home_score   as other_team_score,
             confirmed
        FROM matches
    ) matches
INNER JOIN team ON matches.team_id = team.id
WHERE league_id = 94
    AND season_id = 82
    AND confirmed IS NOT NULL
GROUP BY team.name, team_id
ORDER BY POINTS DESC;
 

Chrome Extension for Freedomain Radio

https://github.com/JonnyD/FreedomainRadio-Chrome-Extension

background.js

jQuery.support.cors = true;         
var feedItems = [];
var podcastUrl = "http://pipes.yahoo.com/pipes/pipe.run?_id=e5f849875fd5f61b23e5d7f79873d9c9&_render=json";
var videoUrl = "http://gdata.youtube.com/feeds/api/users/stefbot/uploads?v=2&alt=jsonc";

function initialise() {
    if (!localStorage.updateInterval) {
        localStorage.updateInterval = 5;
    }
    
    if (!localStorage.types) {
        localStorage.types = "podcast:true;video:true;topic:true";
    }
    
    chrome.browserAction.setBadgeBackgroundColor({color:[255, 102, 0, 255]});
    
    fetchFeeds();
}

function fetchPodcasts(callback) {
    $.ajax({
        url: podcastUrl,
        type: "GET",
        timeout: 30000,
        dataType: "json",
        success: function(data) {
            console.log(data);
            parsePodcasts(data.value, callback);
        },
        error: function(jqXHR, textStatus, ex) {
            console.log(textStatus + "," + ex + "," + jqXHR.responseText);
        }
    });    
}

function parsePodcasts(data, callback) {
    var podcasts = data.items;
    
    var newPodcastItems = [];
    for (var i = 0; i < podcasts.length; i++) {
        var podcast = podcasts[i];
        
        var item = {
            date: convertToUnix(podcast.pubDate), 
            title: podcast.title,
            description: podcast.description,
            thumbnail: '/img/podcast.jpg',
            link: podcast.link
        };      

        newPodcastItems.push(item);
    }
    
    callback(newPodcastItems);
}

function fetchVideos(callback) {
    var xhrFeed = new XMLHttpRequest();
    xhrFeed.onreadystatechange = function() {
        if (xhrFeed.readyState == 4 && xhrFeed.status == 200) {
            parseVideos(xhrFeed.responseText, callback);
        }   
    }
    xhrFeed.open("GET", videoUrl, true);
    xhrFeed.send();                
}

function parseVideos(data, callback) {
    var youtubeJSON = JSON.parse(data);
    var youtubeVideos = youtubeJSON.data.items;
    
    var newVideoItems = [];
    for (var i = 0; i < youtubeVideos.length; i++) {
        var video = youtubeVideos[i];
        
        var item = { 
            date: convertToUnix(video.uploaded), 
            title: video.title,
            description: video.description,
            thumbnail: video.thumbnail.hqDefault,
            link: "http://www.youtube.com/watch?v=" + video.id 
        };
        
        newVideoItems.push(item);
    }
    
    callback(newVideoItems);
}

function fetchFeeds() {
    fetchVideos(function(newVideoItems) {
        fetchPodcasts(function(newPodcastItems) {
            var newFeedItems = newVideoItems.concat(newPodcastItems);
            processLatestFeed(newFeedItems, function() {
              updateBadge();
            });
        });
    });
    
}

function processLatestFeed(newFeedItems, callback) {
    newFeedItems.sort(function(a,b) {return (a.date > b.date) ? -1 : ((b.date > a.date) ? 1 : 0);});
    
    for (var i = 0; i < 25; i++) {
        var newItem = newFeedItems[i];
        
        if (!isItemInFeed(newItem)) {
            newItem.featured = true;
            newItem.description = newItem.description.substring(0,320);
            feedItems.push(newItem);
        }
    }
    
    callback();
}

function isItemInFeed(item) {
    for (i in feedItems) {
        if (feedItems[i]["link"] == item.link) {
            return true;
        }
    }
    return false;
}

function convertToUnix(date) {
  return moment(date).unix()
}

function getFeaturedCount() {
    var featured = 0;
    
    for (i in feedItems) {
        var feedItem = feedItems[i];
        if (feedItem.featured == true) {
            featured++;
        }
    }
    
    return featured;
}

function updateBadge() {
  var featured = getFeaturedCount();
  
  if (featured > 0) {
      chrome.browserAction.setBadgeText({text: featured + ""});
      chrome.browserAction.setTitle({title: featured + " new item" + ((featured > 1) ? "s": "")});
  } else {
      chrome.browserAction.setBadgeText({text: ""});
      chrome.browserAction.setTitle({title: "No new items"});
  }
  
  console.log("badge updated");
}

initialise();

popup.js

document.addEventListener('DOMContentLoaded', function () {
    initialise();
});

function initialise() {
    var background = chrome.extension.getBackgroundPage();
    var latestItems = background.feedItems;
    
    createLatestFeed(latestItems);
    
    background.updateBadge();
}

function createLatestFeed(latestItems) {
    var content = document.getElementById("content");
    var itemView = document.createElement("div");
    itemView.setAttribute("class", "item");
    content.appendChild(itemView);
    
    for (var i = 0; i < 25; i++) {
        var item = latestItems[i];
        var detail = document.createElement("div");
        detail.setAttribute("class", "detail");
        
        if (item["featured"]) {
            detail.setAttribute("class", "featured");
            item["featured"] = false;
        }
        
        var thumbnailLink = document.createElement("a");
        
         if(item["type"] == "podcast"){
            thumbnailLink.setAttribute("href", "#");
            thumbnailLink.setAttribute("onClick", "javascript:window.open('http://fdrpodcast.com/player.php?id=" + item["linkId"] + "','podcastplayer','toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, width=375, height=125');");
        } else {
            thumbnailLink.setAttribute("href", item["link"]); // set link path
            thumbnailLink.setAttribute("target", "_blank");
        }
        
        var thumbnailImg = document.createElement("img");
        thumbnailImg.setAttribute("class", "thumbnail");
        thumbnailImg.setAttribute("onclick", "openTab('" + item["link"] + "');");
        thumbnailImg.setAttribute( "src", item["thumbnail"] );
        thumbnailLink.appendChild(thumbnailImg);
        detail.appendChild(thumbnailLink);
        
        var link = document.createElement("a");
        
        if(item["type"] == "podcast"){
            link.setAttribute("href", "#");
            link.setAttribute("onClick", "javascript:window.open('http://fdrpodcast.com/player.php?id=" + item["linkId"] + "','podcastplayer','toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, width=375, height=125');");
        } else {
            link.setAttribute("href", item["link"]);
            link.setAttribute("target", "_blank");
        }

        var titleNode = document.createElement("div");
        titleNode.setAttribute("class", "title");
        titleNode.appendChild(document.createTextNode(item["title"]));
        link.appendChild(titleNode);
        detail.appendChild(link);
        
        var descriptionNode = document.createElement("div");
        descriptionNode.setAttribute("class", "description");  
       descriptionNode.appendChild(document.createTextNode(item["description"]));
        detail.appendChild(descriptionNode);
        
        itemView.appendChild(detail);
    }
    
}
 

Updating Citadel Schema

Citadel is a Minecraft plugin

Before Update:

-- Citadel 3 Schema
 
CREATE TABLE groups_group (
 `id` INT AUTO_INCREMENT,
 `name` VARCHAR(255) UNIQUE,
 `password` VARCHAR(255),
 `personal` TINYINT(1) DEFAULT 0,
 `status` INT(2) DEFAULT 0,
 `updated` DATETIME,
 `created` DATETIME,
 PRIMARY KEY (id)
);
 
CREATE TABLE groups_member (
 `id` INT AUTO_INCREMENT,
 `name` VARCHAR(32) UNIQUE,
 `updated` DATETIME,
 `created` DATETIME,
 PRIMARY KEY (id)
);
 
CREATE TABLE groups_group_member (
 `member_id` INT,
 `group_id` INT,
 `role` INT DEFAULT 0,
 `updated` DATETIME,
 `created` DATETIME,
 PRIMARY KEY (member_id, group_id)
);
 
ALTER TABLE groups_group_member
ADD FOREIGN KEY (member_id)
REFERENCES groups_member (id);
 
ALTER TABLE groups_group_member
ADD FOREIGN KEY (group_id)
REFERENCES groups_group (id);
 
-- Citadel 3 Upgrade Script
 
-- Migrate members
INSERT INTO groups_member (name, updated, created)
SELECT name, now(), now() FROM member;
 
-- Migrate Groups
INSERT INTO groups_group(name, password, updated, created)
SELECT name, password, now(), now() FROM faction;
 
-- Migrate Personal Groups
UPDATE groups_group
SET personal = 1
WHERE name IN (SELECT name FROM personal_group);
 
-- Migrate Disciplined Groups
UPDATE groups_group g
JOIN faction f on g.name = f.name
SET status = 2
WHERE f.discipline_flags = 1;
 
-- Migrate Group Admins
INSERT INTO groups_group_member(member_id, group_id, role, updated, created)
SELECT gm.id AS member_id, g.id AS group_id, 0, now(), now()
FROM groups_member gm, faction f, groups_group g
WHERE gm.name = f.founder AND g.name = f.name;
 
-- Migrate Group Members
INSERT INTO groups_group_member(member_id, group_id, role, updated, created)
SELECT gm.id AS member_id, g.id AS group_id, 2, now(), now()
FROM faction_member fm, groups_member gm, groups_group g
WHERE gm.name = fm.memberName AND g.name = fm.factionName;
 
-- Migrate Group Moderators
INSERT INTO groups_group_member(member_id, group_id, role, updated, created)
SELECT gm.id AS member_id, g.id AS group_id, 1, now(), now()
FROM moderator m, groups_member gm, groups_group g
WHERE gm.name = m.memberName AND g.name = m.factionName;
 
-- Citadel 3 Schema Selects
SELECT * FROM groups_member;
SELECT * FROM groups_group;
SELECT * FROM groups_group_member;
 

A Finite State Machine in Minecraft

public String first(String text) {
return text.substring(0, 1);
}
 
@EventHandler(priority = EventPriority.HIGHEST)
public void blockPlace(BlockPlaceEvent bpe)
{
    // Block placed by player
    Block block = bpe.getBlock();
 
    // Get the blocks material relative to the block placed by the player
    Material main = block.getType();
    Material up1 = block.getRelative(BlockFace.UP, 1).getType();
    Material up2 = block.getRelative(BlockFace.UP, 2).getType();
    Material down1 = block.getRelative(BlockFace.DOWN, 1).getType();
    Material down2 = block.getRelative(BlockFace.DOWN, 2).getType();
 
    // Concatenate the first letters of each of the materials
    String materialFirstLetters = first(up1.toString()) + first(up2.toString()) +
                                  first(main.toString()) + first(down1.toString()) +
                                  first(down2.toString());
 
    // Regex pattern
    String regex = "DIJ..|I.DJ.|..IDJ";
 
    // Check if the first letters of the materials match the regex pattern
    if (materialFirstLetters.matches(regex))
    {
        System.out.println("Antenna Created");
    }
}