1 Commits

Author SHA1 Message Date
Pepich
7fa6926987 Removed unnessecary debug outputs 2016-04-11 15:41:59 +02:00
9 changed files with 116 additions and 357 deletions

View File

@@ -150,8 +150,7 @@ def toggle_command(sender, command, label, args):
if arg2 == "clear": if arg2 == "clear":
if enabled: if enabled:
del values[uuid] del values[uuid]
return " &aDisabled " + details[1] return " &aDisabled " + details[1]
return " &cAlready disabled: " + details[1]
if arg2 == "details": if arg2 == "details":
return " &aSetting %s:\n &9%s \n&6Accepted arguments: [<slot>|clear|details]" % (setting, details[2]) return " &aSetting %s:\n &9%s \n&6Accepted arguments: [<slot>|clear|details]" % (setting, details[2])

View File

@@ -11,7 +11,6 @@
############################################ ############################################
import os import os
import mysqlhack
import org.bukkit as bukkit import org.bukkit as bukkit
from org.bukkit import * from org.bukkit import *
from helpers import * from helpers import *
@@ -27,7 +26,6 @@ error = colorify("&cUnspecified error")
commands_per_page = 5 commands_per_page = 5
global_aliases = {"./":"/"} global_aliases = {"./":"/"}
data = {} data = {}
# DON'T SET THIS TO TRUE! MySQL requestst are NOT ASYNC yet! (And for some reason it doesn't want to store any data ._.)
use_mysql = False use_mysql = False
# Permissions: # Permissions:
@@ -55,7 +53,6 @@ permission_FINFO = "utils.alias.finfo"
# CODE # # CODE #
######## ########
# OnEnable
enabled = helpers_version in helpers_versions enabled = helpers_version in helpers_versions
if not enabled: if not enabled:
error = colorify("&6Incompatible versions detected (&chelpers.py&6)") error = colorify("&6Incompatible versions detected (&chelpers.py&6)")
@@ -119,19 +116,22 @@ def help(sender, args):
for message in to_display: for message in to_display:
msg(sender, message) msg(sender, message)
if page+1 < pages: if page+1 < pages:
msg(sender, colorify("&6To display the next page, type &c/alias help " + str(page+2))) msg(sender, colorify("&6To display the next page, type &c/help " + str(page+2)))
return True return True
@hook.event("player.PlayerJoinEvent", "high") @hook.event("player.PlayerJoinEvent", "high")
def on_join(event): def on_join(event):
if enabled: try:
t = threading.Thread(target=load_data, args=(uid(event.getPlayer()), )) if enabled:
t.daemon = True t = threading.Thread(target=load_data, args=(uid(event.getPlayer()), ))
t.start() t.daemon = True
else: t.start()
if event.getPlayer().hasPermission(permission_FINFO): else:
disabled_fallback(event.getPlayer()) if event.getPlayer().hasPermission(permission_FINFO):
disabled_fallback(event.getPlayer())
except:
print(trace())
@hook.event("player.AsyncPlayerChatEvent", "high") @hook.event("player.AsyncPlayerChatEvent", "high")
@@ -153,8 +153,7 @@ def on_player_chat(event):
else: else:
event.setMessage(event.getMessage().replace(alias, value)) event.setMessage(event.getMessage().replace(alias, value))
except: except:
print(trace()) return
def hasPerm(player, permission): def hasPerm(player, permission):
return (player.hasPermission(permission)) or (player.hasPermission(permission_ALL)) return (player.hasPermission(permission)) or (player.hasPermission(permission_ALL))
@@ -179,21 +178,15 @@ def add(sender, args):
msg(sender, "&cCould not create alias: Max_limit reached!") msg(sender, "&cCould not create alias: Max_limit reached!")
return True return True
args = [args[0]] + [" ".join(args[1:])] args = [args[0]] + [" ".join(args[1:])]
if not add_alias_data(uid(sender), str(args[0]), args[1]): data[str(uid(sender))][str(args[0])] = args[1]
msg(sender, colorify("&c") + "Could not add an alias for this sequence because a priorly added alias contains it") save_data(uid(sender))
return True
msg(sender, colorify("&7Alias: ") + args[0] + colorify("&7 -> " + args[1] + colorify("&7 was succesfully created!")), usecolor=sender.hasPermission("essentials.chat.color")) msg(sender, colorify("&7Alias: ") + args[0] + colorify("&7 -> " + args[1] + colorify("&7 was succesfully created!")), usecolor=sender.hasPermission("essentials.chat.color"))
return True return True
def radd(sender, args): def radd(sender, args):
args = [args[0:1]] + [" ".join([args[2:len(args)-2]])] + [args[len(args)-1]]
plugin_header(sender, "Alias") plugin_header(sender, "Alias")
args = args[0:2] + [" ".join(args[2:len(args)-1])] + [args[len(args)-1]]
if is_player(sender):
sender_name = colorify(sender.getDisplayName())
else:
sender_name = colorify("&6Console")
target = get_player(args[0])
if args[3].lower() == "false": if args[3].lower() == "false":
plugin_header(target, "Alias") plugin_header(target, "Alias")
msg(target, "&cPlayer " + sender_name + " &cis creating an alias for you!") msg(target, "&cPlayer " + sender_name + " &cis creating an alias for you!")
@@ -202,31 +195,22 @@ def radd(sender, args):
if args[3].lower() == "false": if args[3].lower() == "false":
msg(target, "&cCould not create alias: Max_limit reached!") msg(target, "&cCould not create alias: Max_limit reached!")
return True return True
target = get_player(args[0])
if is_player(sender):
sender_name = colorify(sender.getDisplayName)
else:
sender_name = colorify("&6Console")
if len(args) == 3: if len(args) == 3:
args += ["true"] args += ["true"]
if not add_alias_data(uid(target), str(args[1]), str(args[2])): data[str(uid(target))][str(args[1])] = str(args[2])
message = colorify("&c") + "Could not add an alias for this sequence because a priorly added alias contains it" save_data(uid(target))
msg(sender, message)
if args[3].lower() == "false":
msg(target, message)
return True
msg(sender, colorify("&7Alias: ") + args[1] + colorify("&7 -> " + args[2] + colorify("&7 was succesfully created!")), usecolor=target.hasPermission("essentials.chat.color")) msg(sender, colorify("&7Alias: ") + args[1] + colorify("&7 -> " + args[2] + colorify("&7 was succesfully created!")), usecolor=target.hasPermission("essentials.chat.color"))
if args[3].lower() == "false": if args[3].lower() == "false":
msg(target, colorify("&7Alias: ") + args[1] + colorify("&7 -> " + args[2] + colorify("&7 was succesfully created!")), usecolor=target.hasPermission("essentials.chat.color")) msg(target, colorify("&7Alias: ") + args[1] + colorify("&7 -> " + args[2] + colorify("&7 was succesfully created!")), usecolor=target.hasPermission("essentials.chat.color"))
return True return True
def add_alias_data(puuid, aliased, new_alias):
prior = data[puuid]
if aliased not in prior:
for alias in prior.values():
if aliased in alias:
return False
prior[aliased] = new_alias
save_data(puuid)
return True
def remove(sender, args): def remove(sender, args):
plugin_header(sender, "Alias") plugin_header(sender, "Alias")
try: try:
@@ -241,10 +225,11 @@ def rremove(sender, args):
plugin_header(sender, "Alias") plugin_header(sender, "Alias")
target = get_player(args[0]) target = get_player(args[0])
if is_player(sender): if is_player(sender):
sender_name = colorify(sender.getDisplayName()) sender_name = colorify(sender.getDisplayName)
else: else:
sender_name = colorify("&6Console") sender_name = colorify("&6Console")
if args[2].lower() == "false": if args[2].lower() == "false":
print("WTF")
plugin_header(target, "Alias") plugin_header(target, "Alias")
msg(target, "&cPlayer " + sender_name + " &cis removing an alias for you!") msg(target, "&cPlayer " + sender_name + " &cis removing an alias for you!")
try: try:
@@ -290,21 +275,12 @@ def rlist_alias(sender, args):
def remote(sender, args): def remote(sender, args):
try: try:
return remotes[args[1].lower()](sender, [args[0]] + args[2:]) return remotes[args[1].lower()](sender, [args[0]] + [args[2:]])
except: except:
return subcommands["help"](sender, ["2"]) return subcommands["help"](sender, ["2"])
def load_data(uuid): def load_data(uuid):
try:
load_data_thread(uuid)
# t = threading.Thread(target=load_data_thread, args=(uuid))
# t.daemon = True
# t.start()
except:
print(trace())
def load_data_thread(uuid):
if use_mysql: if use_mysql:
conn = zxJDBC.connect(mysql_database, mysql_user, mysql_pass, "com.mysql.jdbc.Driver") conn = zxJDBC.connect(mysql_database, mysql_user, mysql_pass, "com.mysql.jdbc.Driver")
curs = conn.cursor() curs = conn.cursor()
@@ -319,15 +295,6 @@ def load_data_thread(uuid):
def save_data(uuid): def save_data(uuid):
try:
save_data_thread(uuid)
# t = threading.Thread(target=save_data_thread, args=(uuid))
# t.daemon = True
# t.start()
except:
print(trace())
def save_data_thread(uuid):
if use_mysql: if use_mysql:
conn = zxJDBC.connect(mysql_database, mysql_user, mysql_pass, "com.mysql.jdbc.Driver") conn = zxJDBC.connect(mysql_database, mysql_user, mysql_pass, "com.mysql.jdbc.Driver")
curs = conn.cursor() curs = conn.cursor()
@@ -335,8 +302,8 @@ def save_data_thread(uuid):
else: else:
save_json_file("aliases/" + uuid, data[uuid]) save_json_file("aliases/" + uuid, data[uuid])
# Subcommands: # Subcommands:
subcommands = { subcommands = {
"help": help, "help": help,
"add": add, "add": add,

View File

@@ -89,7 +89,7 @@ def groupchat(sender, message, ann = False):
def do_for_chatgroup(group, func, *args, **kwargs): def do_for_chatgroup(group, func, *args, **kwargs):
for receiver in server.getOnlinePlayers(): for receiver in server.getOnlinePlayers():
if groups.get(uid(receiver)) == group: if groups.get(uid(receiver)) == group:
func(receiver, *args, **kwargs) func(receiver, args, kwargs)
def send_tpa_request(receiver, sender): def send_tpa_request(receiver, sender):
if not receiver == sender: if not receiver == sender:

View File

@@ -47,6 +47,8 @@ def on_dammnspam_command(sender, command, label, args):
timeout_on = round(float(timeout_on), 2) timeout_on = round(float(timeout_on), 2)
timeout_off = timeout_on timeout_off = timeout_on
if 60 >= timeout_on <= -2 or timeout_on == 0: if 60 >= timeout_on <= -2 or timeout_on == 0:
timeout_on = False
if timeout_on == False:
msg(sender, "&cThe timeout must be within 0-60 or -1.") msg(sender, "&cThe timeout must be within 0-60 or -1.")
return True return True
except ValueError: except ValueError:

View File

@@ -5,10 +5,6 @@ friends = open_json_file("friends", {}) # {Player_UUID:[List_of_friend
friend_join_sound = "random.orb" friend_join_sound = "random.orb"
def is_friend_of(player, other):
lst = friends.get(uid(player))
return lst is not None and uid(other) in lst
@hook.event("player.PlayerJoinEvent", "high") # creates sound and sends a bold message on friend join @hook.event("player.PlayerJoinEvent", "high") # creates sound and sends a bold message on friend join
def fjm(event): # friend join message def fjm(event): # friend join message

View File

@@ -314,13 +314,3 @@ def array_to_list(array):
for a in array: for a in array:
return_list += [a] return_list += [a]
return return_list return return_list
#debug wrapper
def debug(func):
def wrap(*args, **kwargs):
try:
func(*args, **kwargs)
except:
print(trace())
return wrap

360
imbusy.py
View File

@@ -1,308 +1,114 @@
################################## # I'M BUSY! Plugin by Curs3d #
# I'M BUSY! Plugin by Curs3d # ##############################
# Concept by CookieManors :D # # Concept by CookieManors :D #
################################## # http://bit.ly/1GnNPW8 #
# This plugin permits users to # ##############################
# send a command that renders # # This plugin permits users to
# them "busy", not letting them # # send a command that renders
# to get tpa requests or direct # # them "busy", not letting them
# messages, except from console. # # to get tpa requests or direct
# On restart, all busy data will # # messages, except from console.
# be cleared. # # On restart, all busy data will
################################## # be cleared.
from helpers import * from helpers import *
from friends import is_friend_of from basecommands import simplecommand
import org.bukkit.command.Command as Command from traceback import format_exc as trace
busy_players = []
imbusy_version = "v1.1.0"
base_permission = "utils.busy" # for /busy status
use_permission = "utils.busy.use" # for being busy
override_permission = "utils.busy.override" # for being able to bother busy people
busy_players = {} # name : false/true where false is normal busy and true is super busy def unclear():
msg(sender, "Umm, what? Sorry, directions unlclear, got head stuck in washing machine")
@hook.command("imbusy", @hook.command("busy",
aliases = ["busy"], aliases = ["focus"],
usage = "/<command> [on, off, status/check]", usage = "/<command> <on|off|status>",
description = "Offers control over your busy status" description = "Sets busy mode on, you cannot recieve tpas and MSGs"
) )
def on_busy_command(sender, cmd, label, args): def on_busy_command(sender, cmd, label, args):
if not is_player(sender): if not is_player(sender):
msg(sender, "&7Sorry, Console cannot be busy") msg(sender, "Sorry, Console cannot be busy")
return True return True
plugin_header(recipient = sender, name = "I'M BUSY!") if not sender.hasPermission("utils.busy.allowed"):
plugin_header(recipient = sender, name = "I'M BUSY!")
#args = array_to_list(args)
if not sender.hasPermission(base_permission):
noperm(sender) noperm(sender)
return True return True
if len(args) == 0: if len(args) == 0:
return toggle(sender) plugin_header(recipient = sender, name = "I'M BUSY!")
msg(sender, "This plugin allows being busy, and when turned on you will not recieve any direct messages or tpa requests.")
arg0 = args[0].lower() msg(sender, "\nCommands:")
if arg0 == "on": msg(sender, "/busy on: turns on busy mode")
return on(sender) msg(sender, "/busy off: turns off busy mode")
if arg0 == "off": msg(sender, "/busy status [player]: shows your or [player]'s current busy status.")
return off(sender)
if arg0 in ("status", "check"):
return status(sender, args[1:])
if arg0 == "super":
return super_cmd(sender)
return help(sender)
def toggle(sender):
if not sender.hasPermission(use_permission):
noperm(sender)
return True return True
sender_name = sender.getName()
if sender_name in busy_players:
del busy_players[sender_name]
broadcast(None, sender.getDisplayName() + " &7is no longer busy...")
else:
busy_players.append(sender_name)
broadcast(None, sender.getDisplayName() + " &7is now busy...")
return True
elif len(args) == 1:
if args[0] == "on":
if sender.getName() in busy_players:
plugin_header(recipient = sender, name = "I'M BUSY!")
msg(sender, "You cannot be even more focused than this without being a jedi!")
return True
busy_players.append(sender.getName())
plugin_header(recipient = sender, name = "I'M BUSY!")
broadcast(None, "%s is now SUPER busy! Don't even TRY bothering them, it will not work!" % sender.getName())
return True
def help(sender): elif args[0] == "off":
msg(sender, "Let's you put yourself in busy status, preventing pms and tpa requests from other players") plugin_header(recipient = sender, name = "I'M BUSY!")
msg(sender, "\n&eCommands:") try:
msg(sender, "&e/busy &7- Toggles busy status") busy_players.remove(sender.getName())
msg(sender, "&e/busy on &7- Turns on busy status") msg(sender, "Master has sent /busy command, %s is freeee!" % sender.getName())
msg(sender, "&e/busy off &7- Turns off busy status") return True
msg(sender, "&e/busy status [player] &7- shows your or [player]'s current busy status") except ValueError:
msg(sender, "&e/busy super &7- sets your status to SUPER busy such that even friends can not bother you") msg(sender, "You are not busy! You cannot be even less busy! Are you perhaps bored?")
return True return True
elif args[0] == "status":
def on(sender): plugin_header(recipient = sender, name = "I'M BUSY!")
if not sender.hasPermission(use_permission): if sender.getName() in busy_players:
noperm(sender) msg(sender, "You are super-duper busy and concentrated right now. Think, think, think!")
return True return True
sender_name = sender.getName()
if busy_players.get(sender_name) is False: # can be None, False or True
msg(sender, "&7You are already busy!")
else:
busy_players[sender_name] = False # busy but not super busy
broadcast(None, sender.getDisplayName() + " &7is now busy...")
return True
def off(sender):
if not sender.hasPermission(use_permission):
noperm(sender)
return True
sender_name = sender.getName()
if sender_name not in busy_players:
msg(sender, "&7You are not busy! You cannot be even less busy! Are you perhaps bored?")
return True
del busy_players[sender_name]
broadcast(None, sender.getDisplayName() + " &7is no longer busy...")
return True
def status(sender, args):
if not sender.hasPermission(base_permission):
noperm(sender)
return True
if len(args) == 0:
if sender.getName() in busy_players:
if busy_players[sender_name] is False:
msg(sender, "&7You are currently busy.")
else: else:
msg(sender, "&7You are currently SUPER busy.") msg(sender, "You are completely unable to focus right now.")
return True
else: else:
msg(sender, "&7You are currently not busy.") plugin_header(recipient = sender, name = "I'M BUSY!")
else: unclear()
target = server.getPlayer(args[0])
if target is None:
msg(sender, "&7That player is not online")
elif target.getName() in busy_players:
if busy_players[target.getName()] is False:
msg(sender, "&7Player %s &7is currently busy." % target.getDisplayName())
else:
msg(sender, "&7Player %s &7is currently SUPER busy." % target.getDisplayName())
else:
msg(sender, "&7Player %s &7is currently not busy." % target.getDisplayName())
return True
def super_cmd(sender):
if not sender.hasPermission(use_permission):
noperm(sender)
return True
sender_name = sender.getName()
if busy_players.get(sender_name) is True:
msg(sender, "&7You are already SUPER busy!")
else:
busy_players[sender_name] = True # SUPER busy
broadcast(None, sender.getDisplayName() + " &7is now SUPER busy...")
return True
@hook.event("player.PlayerQuitEvent", "lowest")
def on_player_leave(event):
player_name = event.getPlayer().getName()
if player_name in busy_players:
del busy_players[player_name]
#---- Dicode for catching any bothering of busy people ----
reply_targets = {}
def can_send(sender, target):
if not target.getName() in busy_players:
return True
if target is sender or sender.hasPermission(override_permission):
return True
return busy_players[target.getName()] is False and is_friend_of(target, sender)
def whisper(sender, target_name):
target = server.getPlayer(target_name)
if target is not None:
if not can_send(sender, target):
msg(sender, "&c[&fBUSY&c] %s&r is busy!" % target.getDisplayName())
return False return False
reply_targets[sender.getName()] = target.getName() elif len(args) == 2 and args[0] == "status":
plugin_header(recipient = sender, name = "I'M BUSY!")
if args[1] in busy_players:
msg(sender, "Yes, %s is busy. Shhh..." % args[1])
return True
else:
msg(sender, "No, you're good. Feel free to chat with %s!" % args[1])
return True
# allow the target to reply regardless of sender being busy else:
if target.getName() in reply_targets: plugin_header(recipient = sender, name = "I'M BUSY!")
del reply_targets[target.getName()] unclear()
return True
def reply(sender):
if sender.getName() in reply_targets:
target = server.getPlayer(reply_targets[sender.getName()])
if target is not None:
if not can_send(sender, target):
msg(sender, "&c[&fBUSY&c] %s&r is busy!" % target.getDisplayName())
return False
# allow the target to reply regardless of sender being busy
if target.getName() in reply_targets:
del reply_targets[target.getName()]
return True
class CommandWrapper(Command):
def __init__(self, wrapped, checker):
Command.__init__(self, wrapped.getName())
self.setDescription(wrapped.getDescription())
self.setPermission(wrapped.getPermission())
self.setUsage(wrapped.getUsage())
self.setAliases(wrapped.getAliases())
self.wrapped = wrapped
self.checker = checker
def execute(self, sender, label, args):
try:
if not is_player(sender) or self.checker(sender, args):
return self.wrapped.execute(sender, label, args)
except:
error(trace())
return True
def tabComplete(self, sender, alias, args):
return self.wrapped.tabComplete(sender, alias, args)
def msg_command_checker(sender, args):
return len(args) <= 1 or whisper(sender, args[0])
def reply_command_checker(sender, args):
return len(args) == 0 or reply(sender)
def tpa_command_checker(sender, args):
if len(args) == 0:
return True
target = server.getPlayer(args[0])
if target is not None and not can_send(sender, target):
msg(sender, "&c[&fBUSY&c] %s&r is busy!" % target.getDisplayName())
return False return False
return True
def tpahere_command_checker(sender, args):
return tpa_command_checker(sender, args)
def mail_command_checker(sender, args):
info("Mail command executed")
if len(args) < 3 or args[0].lower() != "send":
return True
target = server.getPlayer(args[1])
if target is not None and not can_send(sender, target):
msg(sender, "&c[&fBUSY&c] %s&r is busy!" % target.getDisplayName())
return False
return True
@hook.event("player.PlayerCommandPreprocessEvent", "monitor") @hook.event("player.PlayerCommandPreprocessEvent", "monitor")
def on_player_command_preprocess(event): def on_cmd_preprocess_event(event):
message = event.getMessage().split(" ") message = event.getMessage().split(" ")
if len(message) > 1 and message[0].lower() in ("/tell", "/minecraft:tell") and not whisper(event.getPlayer(), message[1]): if message[0] == "/msg" or message[0] == "/w" or message[0] == "/m" or \
event.setCancelled(True) message[0] == "/tell" or message[0] == "/tpa" or message[0] == "/tpahere":
if message[1] in busy_players:
plugin_header(recipient = event.getPlayer(), name = "I'M BUSY!")
@hook.enable msg(event.getPlayer(), "We are sorry, but %s is currently busy. Please try again later." % message[1])
def replace_ess_commands(): event.setCancelled(True)
@hook.event("player.PlayerQuitEvent", "lowest")
def on_player_leave(event):
try: try:
map_field = server.getPluginManager().getClass().getDeclaredField("commandMap") busy_players.remove(event.getPlayer().getName())
map_field.setAccessible(True)
command_map = map_field.get(server.getPluginManager())
commands_field = command_map.getClass().getDeclaredField("knownCommands")
commands_field.setAccessible(True)
map = commands_field.get(command_map)
ess_msg_cmd = map.get("essentials:msg")
ess_reply_cmd = map.get("essentials:reply")
ess_tpa_cmd = map.get("essentials:tpa")
ess_tpahere_cmd = map.get("essentials:tpahere")
ess_mail_cmd = map.get("essentials:mail")
msg_cmd_wrapper = CommandWrapper(ess_msg_cmd, msg_command_checker)
reply_cmd_wrapper = CommandWrapper(ess_reply_cmd, reply_command_checker)
tpa_cmd_wrapper = CommandWrapper(ess_tpa_cmd, tpa_command_checker)
tpahere_cmd_wrapper = CommandWrapper(ess_tpahere_cmd, tpahere_command_checker)
mail_cmd_wrapper = CommandWrapper(ess_mail_cmd, mail_command_checker)
iterator = map.entrySet().iterator()
wrapped_commands = []
while iterator.hasNext():
entry = iterator.next()
value = entry.getValue()
changed = True
if value is ess_msg_cmd:
entry.setValue(msg_cmd_wrapper)
elif value is ess_reply_cmd:
entry.setValue(reply_cmd_wrapper)
elif value is ess_tpa_cmd:
entry.setValue(tpa_cmd_wrapper)
elif value is ess_tpahere_cmd:
entry.setValue(tpahere_cmd_wrapper)
elif value is ess_mail_cmd:
entry.setValue(mail_cmd_wrapper)
else:
changed = False
if changed:
wrapped_commands.append(entry.getKey())
info("[imbusy] wrapped commands: /" + ", /".join(wrapped_commands))
except: except:
error("[Imbusy] Failed to wrap essentials commands") pass
error(trace())

View File

@@ -43,9 +43,7 @@ shared["load_modules"] = [
# Adds /calc, toggles automatic solving of Math expressions in chat # Adds /calc, toggles automatic solving of Math expressions in chat
"calc", "calc",
# Adds aliasing of chat words # Adds aliasing of chat words
"chatalias", #"chatalias",
# For players to point friends
"friends",
# Plugin to locate laggy chunks. /lc <n> lists chunks with more than n entities # Plugin to locate laggy chunks. /lc <n> lists chunks with more than n entities
"lagchunks", "lagchunks",
# Adds /report and /rp, Stores reports with time and location # Adds /report and /rp, Stores reports with time and location

View File

@@ -2,3 +2,4 @@ name: RedstonerUtils
main: main.py main: main.py
version: 3.1.0 version: 3.1.0
author: redstone_sheep author: redstone_sheep