Initial Commit

Initial commit of project.
This commit is contained in:
Alex Stewart
2019-03-09 01:21:44 -08:00
parent e54e9653ed
commit 13420951b1
124 changed files with 5883 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
package core;
import java.util.ArrayList;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
import dataStructures.KittyRole;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
import utils.GlobalLog;
import utils.LogFilter;
// One note about string[] ... We can't change whitelist
// on the command without re-registering it.
public abstract class Command
{
public ArrayList<String> registeredNames;
private KittyRole roleLevel;
private KittyRating contentRating;
public Command(KittyRole roleLevel, KittyRating contentRating)
{
this.registeredNames = new ArrayList<String>();
this.roleLevel = roleLevel;
this.contentRating = contentRating;
}
private void Reject(KittyUser user, String reason)
{
GlobalLog.Warn(LogFilter.Command, this.getClass().getSimpleName() + " from " + user.name + " rejected due to command's " + reason);
}
// Determine if we're exclusive enough for this command and
// if the command is permitted by the guild we're in
private boolean CanCall(KittyGuild guild, KittyChannel channel, KittyUser user)
{
if(guild.contentRating.getValue() < contentRating.getValue())
{
Reject(user, "content rating");
return false;
}
//TODO(wisp, rin): ADD CHANNEL CHECK HERE
if(user.GetRole().getValue() >= roleLevel.getValue())
{
return true;
}
Reject(user, "permissions");
return false;
}
// Called by the Command manager - this will run the command
// if the issuing user has the permission to do so!
protected final void Invoke(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
if(!CanCall(guild, channel, user))
return;
OnRun(guild, channel, user, input, res);
}
public ArrayList<String> RegisteredNames()
{
return registeredNames;
}
public KittyRating Rating()
{
return contentRating;
}
public KittyRole RequiredRole()
{
return roleLevel;
}
// OVERRIDE ME! (This is not required but advised!)
// Returns if the command succeeded or not.
public String HelpText()
{
return "No help text has been added yet for " + this.getClass().getSimpleName() + "!";
}
// OVERRIDE ME! (This is required)
// Returns if the command succeeded or not.
public abstract void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res);
}
+138
View File
@@ -0,0 +1,138 @@
package core;
import java.util.*;
import java.util.Map.Entry;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
import utils.GlobalLog;
import utils.LogFilter;
public class CommandManager
{
// Variables
private HashMap<String, Command> commands;
private ArrayList<CommandThread> threadAccumulator;
private long invokeCount;
// Default Constructor
public CommandManager()
{
commands = new HashMap<String, Command>();
threadAccumulator = new ArrayList<CommandThread>();
invokeCount = 0;
}
// Allows the command manager to keep track of a command.
public void Register(String key, Command command)
{
if(key == null)
return;
key = key.toLowerCase();
command.registeredNames.add(key);
Command old = commands.put(key, command);
if(old != null)
{
GlobalLog.Warn(LogFilter.Core, "Writing over a command with the key " + key);
return;
}
GlobalLog.Log(LogFilter.Core, "Command registered under key " + key);
}
// Registers a command under multiple names!
public void Register(String[] keys, Command command)
{
for(int i = 0; i < keys.length; ++i)
Register(keys[i], command);
}
// Calls the command but on a whole new thread!
public void InvokeOnNewThread(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response responseContext)
{
// This is here to prevent spinning up a thread if this wasn't even a command.
if(input == null || !input.IsValid())
return;
// Spin up a thread and begin it. The thread carries the info needed to invoke the command.
CommandThread newThread = new CommandThread(this, input, guild, channel, user, responseContext);
threadAccumulator.add(newThread);
newThread.start();
}
// Calls the command specified with the key, providing user information arguments, etc.
public void Invoke(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response responseContext)
{
if(input == null || !input.IsValid())
return;
Command command = commands.get(input.key);
if(command != null)
{
++invokeCount;
command.Invoke(guild, channel, user, input, responseContext);
}
else
{
GlobalLog.Warn(LogFilter.Command, "User " + user.name + " tried to invoke command that doesn't exist: " + input.key);
}
}
// Looks up command by name. If it exists, dumps help text, otherwise returns null.
public String GetCommandHelpText(String lookup)
{
Command command = commands.get(lookup);
if(command != null)
return command.HelpText();
return null;
}
// Returns the number of commands sent so far during this program run
public long GetInvokeCount()
{
return invokeCount;
}
// Returns all commands by name
public ArrayList<Command> GetAllRegisteredCommands()
{
ArrayList<Command> cmds = new ArrayList<Command>();
for(Entry<String, Command> entry : commands.entrySet())
cmds.add(entry.getValue());
return cmds;
}
// Info about threads running packaged into an object
public class ThreadData
{
public HashMap<Thread.State, Integer> states = new HashMap<Thread.State, Integer>();
}
public ThreadData DumpThreadData()
{
ThreadData data = new ThreadData();
for(int i = 0; i < threadAccumulator.size(); ++i)
{
Thread.State state = threadAccumulator.get(i).getState();
Integer num = data.states.get(state);
if(num == null)
num = 0;
data.states.put(state, num + 1);
}
return data;
}
}
+56
View File
@@ -0,0 +1,56 @@
package core;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
public class CommandThread extends Thread
{
// Pile of variables
// TODO(wisp): Consider packaging this up into a thread arguments object potentially...?
CommandManager manager;
UserInput input;
KittyGuild guild;
KittyChannel channel;
KittyUser user;
Response response;
// Constructor
public CommandThread(CommandManager manager, UserInput input, KittyGuild guild, KittyChannel channel, KittyUser user, Response response)
{
this.manager = manager;
this.input = input;
this.guild = guild;
this.channel = channel;
this.user = user;
this.response = response;
}
// Method called when spawned as a thread
@Override
public void run()
{
InvokeCommand();
}
private void InvokeCommand()
{
manager.Invoke(guild, channel, user, input, response);
}
// Handles the try catch requirement java has for sleeping
@SuppressWarnings("unused")
private void ThreadSleep(int ms)
{
try
{
Thread.sleep(ms);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
+132
View File
@@ -0,0 +1,132 @@
package core;
import java.sql.ResultSet;
import java.sql.SQLException;
import network.JDBCDriver;
import network.JDBCDriverSQLite;
import utils.GlobalLog;
import utils.LogFilter;
// Java DataBase Connection Driver - the generic version.
// This is where we put everything and swap out the moving parts,
// ie MySQL, PostgreSQL, SQLite, etc...
//
// TODO(wisp): Right now this can be messed up with SQL injection stuff if users
// are allowed to directly touch data. Leaving it like this temporarily.
public class DatabaseDriver
{
private JDBCDriver driver;
// Note: Changing these values can mess up the database...
private final String globalTableName = "kitty_globals";
private final String globalKeyName = "GlobalKey";
private final String globalValueName = "GlobalValue";
public DatabaseDriver()
{
driver = null;
}
public boolean Connect()
{
driver = new JDBCDriverSQLite();
driver.Connect();
// Require a global table if it doesn't exist already
driver.ExecuteStatement("CREATE TABLE IF NOT EXISTS " + globalTableName + " (" + globalKeyName + " text PRIMARY KEY, " + globalValueName + " text);");
return true;
}
// The key will be created if it doesn't exist and the value specified will be stored.
public void CreateSetKey(String key, String value)
{
GlobalLog.Log(LogFilter.Database, "CreateSetKey: Key-" + key + " value-" + value);
if(HasKey(key))
{
UpdateKey(key, value);
}
else
{
CreateKey(key, value);
}
}
// the key will be created if it doesn't exist, and the default value returned.
public String CreateGetKey(String key)
{
GlobalLog.Log(LogFilter.Database, "CreateGeyKey: key-" + key);
if(HasKey(key))
{
return GetKey(key);
}
else
{
String newValue = "";
CreateKey(key, newValue);
return newValue;
}
}
private void UpdateKey(String key, String value)
{
//GlobalLog.Log(LogFilter.Database, "Update key " + key);
String command = "UPDATE " + globalTableName + " SET " + globalValueName + " = '" + value + "' WHERE " + globalKeyName + "= '" + key + "';";
//GlobalLog.Log(LogFilter.Database, "Composed command: " + command);
driver.ExecuteStatement(command);
}
private boolean HasKey(String key)
{
//GlobalLog.Log(LogFilter.Database, "Has key " + key);
String command = "SELECT COUNT(1) as count FROM " + globalTableName + " WHERE " + globalKeyName + " = '" + key + "';";
//GlobalLog.Log(LogFilter.Database, "Composed command: " + command);
ResultSet set = driver.ExecuteReturningStatement(command);
String out = ResultAsString(set, "count");
return out.charAt(0) == '1';
}
private String GetKey(String key)
{
//GlobalLog.Log(LogFilter.Database, "Getting key " + key);
String command = "SELECT " + globalValueName + " as searchedKey FROM " + globalTableName +" WHERE " + globalKeyName + "= \'" + key + "\';";
//GlobalLog.Log(LogFilter.Database, "Composed command: " + command);
ResultSet set = driver.ExecuteReturningStatement(command);
return ResultAsString(set, "searchedKey");
}
private void CreateKey(String key, String value)
{
//GlobalLog.Log(LogFilter.Database, "Creating Key " + key);
String command = "INSERT INTO " + globalTableName + " (GlobalKey, GlobalValue) VALUES ('" + key + "', '" + value + "');";
//GlobalLog.Log(LogFilter.Database, "Composed command: " + command);
driver.ExecuteStatement(command);
}
private String ResultAsString(ResultSet rs, String key)
{
if(rs == null)
return "";
try
{
boolean hasKey = rs.next();
if(hasKey)
{
String val = rs.getString(key);
return val;
}
else
return null;
}
catch (SQLException e)
{
GlobalLog.Error(LogFilter.Database, e.toString());
return null;
}
}
}
+67
View File
@@ -0,0 +1,67 @@
package core;
import java.util.ArrayList;
import utils.GlobalLog;
import utils.LogFilter;
public class DatabaseManager
{
// Singleton accessor
public static DatabaseManager instance = null;
// Private internal variables
private ArrayList<DatabaseTrackedObject> trackedObjects;
private DatabaseDriver driver;
public DatabaseManager()
{
if(instance == null)
{
instance = this;
}
else
{
GlobalLog.Error(LogFilter.Database, "Attempted to register a second DataBase manager!");
return;
}
trackedObjects = new ArrayList<DatabaseTrackedObject>();
driver = new DatabaseDriver();
driver.Connect();
}
// Thumbs through registered objects and syncs them with the database.
// TODO(wisp) Right now this just syncs on the main thread, but we will
// want to have upkeep commands queue up for a dedicated database thread
// in the future to offload the wait times.
public void Upkeep()
{
for(int i = 0 ; i < trackedObjects.size(); ++i)
{
DatabaseTrackedObject dto = trackedObjects.get(i);
if(dto.IsDirty())
{
SetRemoteValue(dto.identifier, dto.Serialize());
dto.Resolve();
}
}
}
public void Register(DatabaseTrackedObject tracked)
{
trackedObjects.add(tracked);
tracked.DeSerialzie(GetRemoteValue(tracked.identifier));
}
public String GetRemoteValue(String key)
{
return driver.CreateGetKey(key);
}
public void SetRemoteValue(String key, String value)
{
driver.CreateSetKey(key, value);
}
}
+35
View File
@@ -0,0 +1,35 @@
package core;
public abstract class DatabaseTrackedObject
{
private boolean isDirty;
public final String identifier;
public DatabaseTrackedObject(String identifier)
{
this.isDirty = false;
this.identifier = identifier;
}
public final boolean IsDirty()
{
return isDirty;
}
public final void MarkDirty()
{
isDirty = true;
}
public final void Resolve()
{
isDirty = false;
}
// TODO(wisp): Not the best way to handle this, potentially consider
// using an object factory that looks up how to serialize and
// deserialize based on the type of the thing being tracked.
// For now, this is fine.
public abstract String Serialize();
public abstract void DeSerialzie(String string);
}
+39
View File
@@ -0,0 +1,39 @@
package core;
public class GenericImage
{
private String artist;
private String postURL;
private String imageURL;
public GenericImage(String artist, String postURL, String imageURL)
{
this.artist = artist;
this.postURL = postURL;
this.imageURL = imageURL;
}
public void editArtist(String artist)
{
this.artist = artist;
}
public void editPostURL(String postURL)
{
this.postURL = postURL;
}
public void editImageURL(String imageURL)
{
this.imageURL = imageURL;
}
public String toString()
{
if(imageURL.isEmpty())
{
return "I couldn't find anything! Please try again!";
}
return "Artist: " + artist + "\n<" + postURL.trim() + ">\n" + imageURL;
}
}
+380
View File
@@ -0,0 +1,380 @@
package core;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
import commands.*;
import dataStructures.*;
import net.dv8tion.jda.core.entities.Member;
import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent;
import utils.GlobalLog;
import utils.LogFilter;
// NOTE(wisp): Isolated factory to assist with storage and caching if needed.
// This also minimizes the number of places JDA interacts with our codebase.
// As it stands, if the object name begins with Kitty, it's constructed here.
// TODO: Make all methods ID based instead of event based
public class ObjectBuilderFactory
{
// Key: guild string id, Value: guild information
private static HashMap<String, KittyGuild> guildCache;
// Key: channel string id, Value: channel information
private static HashMap<String, KittyChannel> channelCache;
// Key: guild string id + user string id, Value: user information
private static HashMap<String, KittyUser> userCache;
// For tracking and managing object sync
private static DatabaseManager database;
// Stats tracking and whatnot... for setting stats too internally potentially
private static Stats stats;
//RPManger for tracking RP system
private static RPManager rpManager;
// NOTE(wisp): This is designed to initialize at the last possible second.
// The idea behind this is that there may be other things that may need
// time to initialize before the factory can use them, and this guarantees
// they get the time they need.
private static boolean hasInitialized;
private static Semaphore initMutex = new Semaphore(1);
private static void LazyInit()
{
if(hasInitialized)
return;
try
{
initMutex.acquire();
try
{
// NOTE(wisp): Actually put all the init code here.
// In the future, this is where we would read from something external.
guildCache = new HashMap<String, KittyGuild>();
userCache = new HashMap<String, KittyUser>();
channelCache = new HashMap<String, KittyChannel>();
database = null;
stats = null;
}
finally
{
initMutex.release();
}
}
catch(InterruptedException ie)
{
GlobalLog.Error(LogFilter.Core, "Issue during object builder lazy initialization."
+ " The factory was not initialized, "
+ "and kitty will not be able to continue functionally.");
}
hasInitialized = true;
}
// Explicitly locks: guildCache
public static KittyGuild ExtractGuild(GuildMessageReceivedEvent event)
{
LazyInit();
// Look up the guild. This process can only happen in a single-threaded way
// because of the nature of the cache. We wait until the last second to
// look up the guild.
String uid = event.getGuild().getId();
// ince we're lazily initialized, we can synchronize w/ the
// guildCache object now instead of having to use a mutex.
KittyGuild guild = null;
synchronized (guildCache)
{
KittyGuild cachedGuild = guildCache.get(uid);
if(cachedGuild != null)
{
guild = cachedGuild;
}
else
{
// Construct a new guild with defaults
guild = new KittyGuild(uid);
DatabaseManager.instance.Register(guild);
guildCache.put(uid, guild);
}
}
return guild;
}
// Explicitly locks: guildCache
public static KittyRole ExtractRole(GuildMessageReceivedEvent event)
{
LazyInit();
// Looks up the user role. If none is found we check to see if they own
// the guild if not, they're assumed to be
// allowed to use the bot at a general level.
KittyRole role = KittyRole.General;
if(event.getAuthor().getId() == event.getGuild().getOwner().getUser().getId())
{
role = KittyRole.Admin;
}
String uid = event.getGuild().getId() + event.getAuthor().getId();
synchronized(userCache)
{
KittyUser cachedUser = userCache.get(uid);
if(cachedUser != null)
role = cachedUser.GetRole();
}
return role;
}
// Explicitly locks: guildCache
// Extracts the content rating information it can from the provided event.
public static KittyRating ExtractContentRating(GuildMessageReceivedEvent event)
{
LazyInit();
// Look up content rating of the guild, returns a safe content rating.
KittyRating contentRating = KittyRating.Safe;
String uid = event.getGuild().getId();
synchronized(guildCache)
{
KittyGuild cachedGuild = guildCache.get(uid);
if(cachedGuild != null)
contentRating = cachedGuild.contentRating;
}
return contentRating;
}
// Implicitly locks guild cache by calling ExtractGuild
public static KittyChannel ExtractChannel(GuildMessageReceivedEvent event)
{
LazyInit();
String channelID = event.getChannel().getId();
String guildID = event.getGuild().getId();
KittyChannel channel = null;
synchronized(channelCache)
{
KittyChannel cachedChannel = channelCache.get(channelID);
if(cachedChannel != null)
{
channel = cachedChannel;
}
else
{
KittyGuild cachedGuild = guildCache.get(guildID);
channel = new KittyChannel(channelID, cachedGuild);
channelCache.put(channelID, channel);
}
}
return channel;
}
// Implicitly locks guild cache by calling ExtractRole and ExtractGuild
public static KittyUser ExtractUser(GuildMessageReceivedEvent event)
{
LazyInit();
String uid = event.getGuild().getId() + event.getAuthor().getId();
KittyUser user = null;
synchronized(userCache)
{
KittyUser cachedUser = userCache.get(uid);
if(cachedUser != null)
{
updateUser(cachedUser, event.getMember());
user = cachedUser;
}
else
{
KittyRole role = ExtractRole(event);
KittyGuild guild = ExtractGuild(event);
String name;
if(event.getMember().getNickname() == null)
name = event.getAuthor().getName();
else
name = event.getMember().getNickname();
String discordID = event.getMember().getUser().getId();
String avatarID = event.getAuthor().getAvatarUrl();
user = new KittyUser(name, guild, role, uid, avatarID, discordID);
DatabaseManager.instance.Register(user);
userCache.put(uid, user);
}
}
if(event.getMessage().getMentionedMembers().isEmpty())
return user;
Member mentioned;
for(int i = 0; i < event.getMessage().getMentionedMembers().size(); i++)
{
mentioned = event.getMessage().getMentionedMembers().get(i);
if(mentioned.getNickname() != null)
ExtractUserByJDAUser(event.getGuild().getId(), mentioned.getNickname(),
mentioned.getUser().getId(), mentioned.getUser().getAvatarUrl(), mentioned.getUser().getId());
else
ExtractUserByJDAUser(event.getGuild().getId(), mentioned.getUser().getName(),
mentioned.getUser().getId(), mentioned.getUser().getAvatarUrl(), mentioned.getUser().getId());
}
return user;
}
public static KittyUser ExtractUserByJDAUser(String guildID, String name, String userID, String avatarID, String discordID)
{
LazyInit();
String uid = guildID + userID;
KittyUser user = null;
synchronized(userCache)
{
KittyUser cachedUser = userCache.get(uid);
if(cachedUser != null)
{
if(name != null)
cachedUser.name = name;
cachedUser.avatarID = avatarID;
user = cachedUser;
}
else
{
KittyRole role = KittyRole.General;
KittyGuild guild = guildCache.get(guildID);
user = new KittyUser(name, guild, role, uid, avatarID, discordID);
DatabaseManager.instance.Register(user);
userCache.put(uid, user);
}
}
return user;
}
public static KittyUser getCachedUser(String guildID, String userID)
{
String uid = guildID + userID;
KittyUser user = null;
synchronized(userCache)
{
user = userCache.get(uid);
}
return user;
}
public static void updateUser(KittyUser user, Member member)
{
if(member.getNickname() == null)
user.name = member.getUser().getName();
else
user.name = member.getNickname();
user.avatarID = member.getUser().getAvatarUrl();
}
// Default construction of the command manager.
// TODO(wisp): We want to be able to keep all this data
// stored off in a file at some point, so we can reflect it onto the
// project and build it per-guild. That's for later now tho.
public static CommandManager ConstructCommandManager()
{
LazyInit();
CommandManager manager = new CommandManager();
manager.Register("test", new CommandTesting(KittyRole.Dev, KittyRating.Safe));
manager.Register("work", new CommandDoWork(KittyRole.Dev, KittyRating.Safe));
manager.Register("shutdown", new CommandShutdown(KittyRole.Dev, KittyRating.Safe));
manager.Register("stats", new CommandStats(KittyRole.Dev, KittyRating.Safe));
manager.Register("invite", new CommandInvite(KittyRole.Dev, KittyRating.Safe));
manager.Register("buildHelp", new CommandHelpBuilder(KittyRole.Dev, KittyRating.Safe));
manager.Register("rating", new CommandRating(KittyRole.Admin, KittyRating.Safe));
manager.Register("indicator", new CommandChangeIndicator(KittyRole.Admin, KittyRating.Safe));
manager.Register("poll", new CommandPollManage(KittyRole.Mod, KittyRating.Safe));
manager.Register("givebeans", new CommandGiveBeans(KittyRole.Mod, KittyRating.Safe));
manager.Register("rpg", new CommandRPG(KittyRole.Mod, KittyRating.Safe));
manager.Register(new String[]{"perish", "thenperish"}, new CommandPerish(KittyRole.General, KittyRating.Safe));
manager.Register("yeet", new CommandYeet(KittyRole.General, KittyRating.Safe));
manager.Register("ping", new CommandPing(KittyRole.General, KittyRating.Safe));
manager.Register("boop", new CommandBoop(KittyRole.General, KittyRating.Safe));
manager.Register("roll", new CommandRoll(KittyRole.General, KittyRating.Safe));
manager.Register("choose", new CommandChoose(KittyRole.General, KittyRating.Safe));
manager.Register("help", new CommandHelp(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"info", "about"}, new CommandInfo(KittyRole.General, KittyRating.Safe));
manager.Register("vote", new CommandPollVote(KittyRole.General, KittyRating.Safe));
manager.Register("results", new CommandPollResults(KittyRole.General, KittyRating.Safe));
manager.Register("showpoll", new CommandPollShow(KittyRole.General, KittyRating.Safe));
manager.Register("wolfram", new CommandWolfram(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"c++", "g++", "cplus",}, new CommandColiru(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"java", "jdoodle" }, new CommandJDoodle(KittyRole.General, KittyRating.Safe));
manager.Register("beans", new CommandBeansShow(KittyRole.General, KittyRating.Safe));
manager.Register("role", new CommandRole(KittyRole.General, KittyRating.Safe));
manager.Register("bet", new CommandBetBeans(KittyRole.General, KittyRating.Safe));
manager.Register("map", new CommandMap(KittyRole.General, KittyRating.Safe));
manager.Register("rpstart", new CommandRPStart(KittyRole.General, KittyRating.Safe));
manager.Register("rpend", new CommandRPEnd(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"tony", "stark", "dontfeelgood", "dontfeelsogood"}, new CommandStark(KittyRole.General, KittyRating.Safe));
manager.Register("blur", new CommandBlurry(KittyRole.General, KittyRating.Safe));
return manager;
}
// NOTE(wisp): Default database manager construction. It can be constructed
// in different ways, and so we construct it outside of the constructor for
// the factory since it doesn't have to be present / can be elsewhere.
// Effectively we cache the database here.
public static DatabaseManager ConstructDatabaseManager()
{
LazyInit();
if(database == null)
database = new DatabaseManager();
return database;
}
public static Stats ConstructStats(CommandManager manager)
{
LazyInit();
if(stats == null)
stats = new Stats(manager);
return stats;
}
public static RPManager ConstructRPManager()
{
LazyInit();
if(rpManager == null)
rpManager = new RPManager();
return rpManager;
}
public static Integer GetGuildCount()
{ synchronized(guildCache)
{
return guildCache.size();
}
}
public static Integer GetUserCount()
{ synchronized(userCache)
{
return userCache.size();
}
}
}
+83
View File
@@ -0,0 +1,83 @@
package core;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import dataStructures.*;
import net.dv8tion.jda.core.JDA;
import utils.GlobalLog;
import utils.LogFilter;
public class RPManager
{
static HashMap <Long, KittyRP> logs = new HashMap<Long,KittyRP> ();
public static RPManager instance = null;
public RPManager()
{
if(instance == null)
{
instance = this;
}
else
{
GlobalLog.Error(LogFilter.Core, "Attempted to create a second RP Manager!");
return;
}
}
public String newRP(KittyChannel channel, ArrayList <KittyUser> users)
{
if(logs.containsKey(Long.parseLong(channel.uniqueID)))
return "You can't have 2 RP's running at the same time!";
else
logs.put(Long.parseLong(channel.uniqueID), new KittyRP(users, channel));
return "RP started!";
}
public void addLine(KittyChannel channel, KittyUser user, UserInput input)
{
if(logs.containsKey(Long.parseLong(channel.uniqueID)) && !input.IsValid())
logs.get(Long.parseLong(channel.uniqueID)).addLine(user, input.message);
}
public File endRP(KittyChannel channel, KittyUser user) throws FileNotFoundException, UnsupportedEncodingException
{
if(logs.containsKey(Long.parseLong(channel.uniqueID)))
{
return null;
}
File log = logs.get(Long.parseLong(channel.uniqueID)).endRP(user);
if(log != null)
logs.remove(Long.parseLong(channel.uniqueID));
return log;
}
public static void Upkeep(JDA kitty)
{
Response res = new Response(null, kitty);
String reminder = "";
ArrayList<Long> users;
long currentTime = System.currentTimeMillis();
for (Entry<Long, KittyRP> entry : logs.entrySet())
{
if(currentTime > entry.getValue().getTimer() + 1000 * 60 * 30)
{
reminder = "Don't forget about your RP log!";
users = entry.getValue().getUsers();
for(int i = 0; i < users.size(); i ++)
{
reminder += " <@" + users.get(i) + ">";
}
res.CallToChannel(reminder, entry.getValue().getChannel().uniqueID);
reminder = "";
entry.getValue().resetTimer();
}
}
}
}
+126
View File
@@ -0,0 +1,126 @@
package core;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import core.CommandManager.ThreadData;
import utils.GlobalLog;
import utils.LogFilter;
// NOTE(wisp): This is a class designed to be asked about various kittybot stats
public class Stats
{
public static String botName = "KittyBot";
public static Stats instance = null;
// Internal
private boolean isShuttingDown;
private long messagesSeen;
private long initTimeMS;
private CommandManager commandManager;
private OperatingSystemMXBean osBean;
public Stats(CommandManager manager)
{
if(instance == null)
{
instance = this;
}
else
{
GlobalLog.Error(LogFilter.Core, "Attempted to create a second Stats singleton!");
return;
}
isShuttingDown = false;
messagesSeen = 0;
initTimeMS = System.currentTimeMillis();
commandManager = manager;
osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
}
public void NoteMessageEvent()
{
++messagesSeen;
}
public void IndicateShutdown()
{
synchronized(instance)
{
isShuttingDown = true;
}
}
public boolean GetIsShuttingDown()
{
synchronized(instance)
{
return isShuttingDown;
}
}
/////////////////////////////////////////
// All the functions to look up stats! //
/////////////////////////////////////////
// Formatted as HH:MM:SS
public String GetFormattedUptime()
{
long dif = System.currentTimeMillis() - initTimeMS;
return String.format("%02d:%02d:%02d",
TimeUnit.MILLISECONDS.toHours(dif),
TimeUnit.MILLISECONDS.toMinutes(dif) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(dif)),
TimeUnit.MILLISECONDS.toSeconds(dif) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(dif)));
}
// Get number of commands that kitty has run!
public long GetCommandsProcessed()
{
return commandManager.GetInvokeCount();
}
public long GetMessagesSeen()
{
return messagesSeen;
}
public double GetSystemCPULoad()
{
return osBean.getSystemLoadAverage();
}
public long GetCPUAvailable()
{
return osBean.getAvailableProcessors();
}
public ThreadData GetThreadData()
{
return commandManager.DumpThreadData();
}
public int GetGuildCount()
{
return ObjectBuilderFactory.GetGuildCount();
}
public int GetUserCount()
{
return ObjectBuilderFactory.GetUserCount();
}
public ArrayList<Command> GetAllCommands()
{
return commandManager.GetAllRegisteredCommands();
}
public String GetHelpText(String commandName)
{
return commandManager.GetCommandHelpText(commandName);
}
}
+14
View File
@@ -0,0 +1,14 @@
package core.rpg;
public class RPGArmor extends RPGItem
{
int defense;
public RPGArmor()
{
super("Tattered Clothes", "The remains of your first sewing project. You did a pretty good job!", 5);
defense = 1;
}
public long GetDefense() { return defense; }
}
+11
View File
@@ -0,0 +1,11 @@
package core.rpg;
public class RPGBattleContext
{
public RPGState stateRef;
public RPGBattleContext(RPGState state)
{
this.stateRef = state;
}
}
+9
View File
@@ -0,0 +1,9 @@
package core.rpg;
public abstract class RPGCommand
{
public RPGCommand() { }
// OVERRIDE ME
public abstract String OnRun(RPGState state, RPGInput input);
}
+9
View File
@@ -0,0 +1,9 @@
package core.rpg;
public class RPGEnemy extends RPGUnit
{
RPGWeapon attack;
RPGArmor armor;
long expValue;
}
+65
View File
@@ -0,0 +1,65 @@
package core.rpg;
public final class RPGExpTable
{
public static long EXPFloor(long level)
{
if(level > Levels.length - 1)
level = Levels.length - 1;
else if (level <= 0)
level = 1;
return Levels[(int) level];
}
public static long EXPCeil(long level)
{
if(level > Levels.length - 1)
level = Levels.length - 1;
else if (level <= 0)
level = 1;
return Levels[(int) (level + 1)];
}
public static long LevelFromEXP(long exp)
{
if(exp < 0)
exp = 0;
for(int i = 0; i < Levels.length; ++i)
{
if(Levels[i] > exp)
return i - 1;
}
return Levels.length - 1;
}
// Based on D&D Pathfinder levels
public static long Levels[] =
{
0, // level 0
0, // level 1
3000, // level 2
7500, // level 3
14000, // level 4
23000, // level 5
35000, // level 6
53000, // level 7
77000, // level 8
115000, // level 9
160000, // level 10
235000, // level 11
330000, // level 12
475000, // level 13
665000, // level 14
955000, // level 15
1350000, // level 16
1900000, // level 17
2700000, // level 18
3850000, // level 19
5350000 // level 20
};
}
+82
View File
@@ -0,0 +1,82 @@
package core.rpg;
import java.util.HashMap;
import commands.rpg.RPGCommandBattleFight;
import commands.rpg.RPGCommandBattleRun;
import commands.rpg.RPGCommandExplore;
import commands.rpg.RPGCommandInfo;
import commands.rpg.RPGCommandStats;
import core.rpg.RPGInput;
// Holds the framework for the text RPG, for any number of users
public class RPGFramework
{
// User ID to user state. Users are conceptually just an ID.
public HashMap<String, RPGState> gameStates;
public HashMap<String, RPGCommand> gameCommands;
// Ctor
public RPGFramework()
{
this.gameStates = new HashMap<String, RPGState>();
this.gameCommands = new HashMap<String, RPGCommand>();
RegisterCommand("stats", new RPGCommandStats());
RegisterCommand("about", new RPGCommandInfo());
RegisterCommand("info", new RPGCommandInfo());
RegisterCommand("explore", new RPGCommandExplore());
RegisterCommand("run", new RPGCommandBattleRun());
RegisterCommand("fight", new RPGCommandBattleFight());
}
// Primary external
public String Run(String userID, String inputRaw)
{
RPGState state = LookupState(userID);
RPGInput input = new RPGInput(inputRaw);
return ExecuteCommand(input.key, state, input);
}
// Get state for executing a command
public RPGState LookupState(String userID)
{
RPGState state;
synchronized(gameStates)
{
// Note: Hardcoded right now. Later: Extract.
state = gameStates.get(userID);
if(state == null)
{
state = new RPGState(userID);
gameStates.put(userID, state);
}
}
return state;
}
// Registers a command
private void RegisterCommand(String commandName, RPGCommand command)
{
commandName = commandName.toLowerCase();
if(gameCommands.put(commandName, command) != null)
RPGLog.Log("Managed to register the same RPG command twice! Not ideal!");
RPGLog.Log("Registered " + commandName);
}
private String ExecuteCommand(String name, RPGState state, RPGInput input)
{
synchronized(gameCommands)
{
RPGCommand command = gameCommands.get(name.toLowerCase());
if(command != null && state != null)
return command.OnRun(state, input);
}
return null;
}
}
+42
View File
@@ -0,0 +1,42 @@
package core.rpg;
public class RPGInput
{
public String raw;
public String key;
public String value;
public RPGInput(String raw)
{
this.raw = raw;
this.key = "";
this.value = "";
if(raw == null || raw.length() == 0)
return;
raw = raw.trim();
int whitespacePos = FindFirstWhitespace(raw);
if(whitespacePos == -1)
{
key = raw;
return;
}
key = raw.substring(0, whitespacePos).trim();
value = raw.substring(whitespacePos).trim();
}
// Finds first whitespace in the string
private int FindFirstWhitespace(String str)
{
for (int i = 0; i < str.length(); ++i)
{
if (Character.isWhitespace(str.charAt(i)))
return i;
}
return -1;
}
}
+25
View File
@@ -0,0 +1,25 @@
package core.rpg;
public abstract class RPGItem
{
protected String name;
protected String description;
protected int value;
// Defaults
public RPGItem() { this("unknown"); }
public RPGItem(String name) { this(name, "nothig is known about this item"); }
public RPGItem(String name, String description) { this(name, description, 1); }
// Ctor
public RPGItem(String name, String description, int value)
{
this.name = name;
this.description = description;
this.value = value;
}
public String GetName() { return name; }
public String GetDescription() { return description; }
public long GetValue() { return value; }
}
+12
View File
@@ -0,0 +1,12 @@
package core.rpg;
import utils.GlobalLog;
import utils.LogFilter;
public class RPGLog
{
public static void Log(String toWrite)
{
GlobalLog.Log(LogFilter.Command, "[RPG] " + toWrite);
}
}
+38
View File
@@ -0,0 +1,38 @@
package core.rpg;
import java.util.Scanner;
public class RPGMain
{
private static RPGFramework framework;
private static Scanner scanner;
public static void RPGmain(String[] args)
{
Init();
while(Run()) { };
Shutdown();
}
// Initializes stuff (small factory, ish)
private static void Init()
{
framework = new RPGFramework();
scanner = new Scanner(System.in);
}
// Main loop
private static boolean Run()
{
// move outside
String out = framework.Run("Test", scanner.nextLine());
RPGLog.Log(out);
return true;
}
// Cleanup
private static void Shutdown()
{
scanner.close();
}
}
+58
View File
@@ -0,0 +1,58 @@
package core.rpg;
public class RPGPlayer extends RPGUnit
{
private String name;
private long gold;
private long exp;
RPGWeapon weapon;
RPGArmor armor;
public RPGPlayer()
{
super();
name = "Wanderer";
healthCurrent = 5;
healthMax = 5;
gold = 0;
weapon = new RPGWeapon();
armor = new RPGArmor();
}
// Getters
public long GetEXP() { return exp; }
public long GetGold() { return gold; }
public String GetName() { return name; }
public RPGArmor GetArmor() { return armor; };
public RPGWeapon GetWeapon() { return weapon; };
// Setters
public void SetName(String name) { this.name = name; }
// Interactions
public void ApplyEXP(int expToGive)
{
if(expToGive < 0)
expToGive = 0;
exp += expToGive;
}
public void GiveGold(long amount)
{
if(amount < 0)
amount = 0;
gold += amount;
}
public void SpendGold(long amount)
{
if(amount < 0)
amount = 0;
gold -= amount;
}
}
+19
View File
@@ -0,0 +1,19 @@
package core.rpg;
// Holds specific state information for a given user and their world
public class RPGState
{
// General
public String userID;
// Stats and gameplay
public RPGPlayer player;
public RPGBattleContext battleContext;
public RPGState(String userID)
{
this.userID = userID;
this.player = new RPGPlayer();
this.battleContext = null;
}
}
+34
View File
@@ -0,0 +1,34 @@
package core.rpg;
public abstract class RPGUnit
{
protected int healthMax;
protected int healthCurrent;
public int GetHealthMax() { return healthMax; }
public int GetHealthCurrent() { return healthCurrent; }
public boolean IsAlive() { return healthCurrent > 0; }
// Generic implementation. Consider implementing armor in
// derived classes, for example.
public void ApplyDamage(int value)
{
if(value < 0)
value = 0;
healthCurrent -= value;
}
// Generic implementation. Consider applying boosts in
// derived classes, for example.
public void ApplyHealing(int value)
{
if(value < 0)
value = 0;
healthCurrent += value;
if(healthCurrent > healthMax)
healthCurrent = healthMax;
}
}
+17
View File
@@ -0,0 +1,17 @@
package core.rpg;
public class RPGWeapon extends RPGItem
{
private long attack;
private double accuracy;
RPGWeapon()
{
super("Singed Stick", "A really cool stick that you found! You poked at your campfire last night with it a bit, so the end is a bit toasty.", 2);
attack = 1;
accuracy = 0.8;
}
public long GetAttack() { return attack; }
public double GetAccuracy() { return accuracy; }
}