Skip to content

Instantly share code, notes, and snippets.

@ntoonio
Last active June 6, 2019 22:16
Show Gist options
  • Save ntoonio/595921fccb540680e32edc7c90a8dddc to your computer and use it in GitHub Desktop.
Save ntoonio/595921fccb540680e32edc7c90a8dddc to your computer and use it in GitHub Desktop.
A python 3 script for controlling a Ultra Hardcore game in Minecraft
#!/usr/bin/python3
from mcrcon import MCRcon
import json
import datetime
import time
import os
import json
import math
import argparse
import random
import string
ALL_COLORS = ["aqua", "black", "blue", "dark_aqua", "dark_blue", "dark_gray", "dark_green", "dark_purple", "dark_red", "gold", "gray", "green", "light_purple", "red", "white", "yellow"]
PROPERTIES_SETTINGS = {"spawn-protection": "0", "pvp": "true", "enable-rcon": "true", "enable-command-block": "true"}
conf = {
"host": "localhost",
"start_time": None,
"spawn_point": {
"x": "0",
"z": "0"
},
"worldborder": {
"start_shrink": "after",
"time": "0:01",
"first_size": "2000",
"second_size": "200",
"shrink_time": "0:01",
"cap_speed": "2.5",
"damage_buffer": "3",
"damage_per_block": "0.5",
"warning_distance": "10",
"warning_time": "15"
},
"gamerules": {
"announceAdvancements": "true",
"spawnRadius": "0"
},
"prop": {}
}
teams = {}
def setupSubparsers(parser, T):
if "subparsers" in T:
sub = parser.add_subparsers(dest=T["name"])
for t in T["subparsers"]:
tParser = sub.add_parser(t["name"])
if "arguments" in t:
for a in t["arguments"]:
name = a["name"]
b = a.copy()
del b["name"]
tParser.add_argument(name, **b)
setupSubparsers(tParser, t)
def runAction(args, T):
for t in T["subparsers"]:
if t["name"] == getattr(args, T["name"]):
if "cmd" in t:
a = vars(args)
b = [a[k] for k in a if k in [x["name"] for x in t["arguments"]]] if "arguments" in t else []
keys = [x["name"] for x in t["arguments"] if x["name"].startswith("-")]
values = [a[k.lstrip("-")] for k in keys]
c = {k.lstrip("-"):v for k,v in zip(keys, values)}
t["cmd"](*b, **c)
elif "subparsers" in t:
runAction(args, t)
def parseArgTree(tree):
parser = argparse.ArgumentParser()
setupSubparsers(parser, tree)
args = parser.parse_args()
runAction(args, tree)
def mergeDict(d, d2):
for k, v in d2.items():
if (k in d and isinstance(d[k], dict) and isinstance(d2[k], dict)):
mergeDict(d[k], d2[k])
else:
d[k] = d2[k]
def getDictPath(path, d):
if path.find("/") >= 0:
split = path.split("/", 1)
return getDictPath(split[1], d[split[0]])
else:
if path in d:
return d[path]
else:
return None
def getConf(path):
return getDictPath(path, conf)
def createCon():
return MCRcon(conf["host"], getConf("prop/rcon.password"), port=int(getConf("prop/rcon.port")))
def runCommand(cmd, mcr = None):
if mcr == None:
with createCon() as mcr:
return mcr.command(cmd)
else:
return mcr.command(cmd)
def say(s, mcr = None):
runCommand('/tellraw @a ["<", {"text": "UHC-controller", "color":"gold"}, "> ' + s + '"]', mcr)
def getBorder(mcr = None):
resp = runCommand("/worldborder get", mcr)
return int(resp.split()[5])
def setBorder(size, time, mcr = None):
runCommand("/worldborder set " + str(size) + " " + str(time), mcr)
def zeroPad(s, w = 2):
return ("0" * (w - len(str(s)))) + str(s)
def timeToSeconds(t):
tt = datetime.datetime.strptime(t, "%H:%M").timetuple()
return tt.tm_hour * 60 * 60 + tt.tm_min * 60
def secondsToTime(s):
hours = math.floor(s / (60 * 60))
minutes = math.floor((s - hours * 60 * 60) / 60)
seconds = math.floor(s - hours * 60 * 60 - minutes * 60)
return zeroPad(hours) + ":" + zeroPad(minutes) + (":" + zeroPad(seconds) if seconds > 0 else "")
## Properties setup
def propertiesSetup():
if os.path.exists("server.properties"):
props = {}
with open("server.properties") as f:
for line in f.readlines():
if line.startswith("#"): continue
parts = line.split("=", 1)
props[parts[0]] = parts[1].rstrip()
for p in PROPERTIES_SETTINGS:
props[p] = PROPERTIES_SETTINGS[p]
props["rcon.port"] = input("Which port do you want to use for rcon? >")
rconPass = "".join(random.choice(string.ascii_letters + string.digits) for _ in range(30))
props["rcon.password"] = rconPass
print("Your rcon password is " + rconPass)
with open("server.properties", "w") as f:
out = "#Minecraft server properties"
for p in props:
out += "\n" + p + "=" + props[p]
f.write(out)
print("Changed some properties")
else:
print("Couldn't find a server.properties file")
def gameruleSetup():
with createCon() as mcr:
mcr.command("/gamerule naturalRegeneration false")
mcr.command("/worldborder center " + getConf("spawn_point/x") + " " + getConf("spawn_point/z"))
mcr.command("/worldborder set " + getConf("worldborder/first_size"))
mcr.command("/worldborder warning distance " + getConf("worldborder/warning_distance"))
mcr.command("/worldborder warning time " + getConf("worldborder/warning_time"))
mcr.command("/worldborder damage buffer " + getConf("worldborder/damage_buffer"))
mcr.command("/worldborder damage amount " + getConf("worldborder/damage_per_block"))
mcr.command("/gamerule doDaylightCycle false")
mcr.command("/time set day")
## Team setup
def teamsSetup(teamFileName = None):
print("Which teams do you want? Enter a color and then hit 'Enter'. It will be listening for names until 'end' is entered.")
while True:
a = input(">").lower()
if a == "end":
if len(teams) <= 1:
print("You need more than one team. Can't end now")
else: break
if a in ALL_COLORS:
if not a in teams:
teams[a] = []
print("Selected '" + a + "'")
else:
del teams[a]
print(a + " was already selected. Unselected it!")
else:
print(a + " isn't a recognized color")
for t in teams:
while True:
print("Who do you wan't to be on team " + t + "? Go to next team by entering 'next'.")
name = input(">")
if name == "next":
break
elif not name in teams[t]:
teams[t].append(name)
print("Added " + name + " to team " + t)
else:
teams[t].remove(name)
print(name + " was already on team " + t + ". Removed that player from team " + t)
teamFileName = teamFileName if teamFileName is not None else "uhc_teams.json"
with open(teamFileName, "w") as f:
f.write(json.dumps(teams))
## Game loop
def gameLoop():
startTimestamp = time.mktime(datetime.datetime.strptime(getConf("start_time"), "%Y-%m-%d %H:%M").timetuple())
if startTimestamp - time.time() > 0:
gameruleSetup()
with createCon() as mcr:
for t in teams:
mcr.command("/team add " + t)
mcr.command("/team modify " + t + " color " + t)
for p in teams[t]:
mcr.command("/team join " + t + " " + p)
say("Waiting for game to start at " + getConf("start_time"), mcr)
time.sleep(startTimestamp - time.time())
with createCon() as mcr:
mcr.command("/advancement revoke @a everything")
mcr.command("/effect clear @a")
mcr.command("/clear @a")
mcr.command("/time set day")
mcr.command("/gamerule doDaylightCycle true")
# 4 is just an arbitrary number and can be what ever
distanceBetweenPlayers = math.ceil(int(getConf("worldborder/first_size")) / 4)
maxRange = int(int(getConf("worldborder/first_size")) / 2)
mcr.command("/spreadplayers " + getConf("spawn_point/x") + " " + getConf("spawn_point/z") + " " + str(distanceBetweenPlayers) + " " + str(maxRange) + " true @a")
else:
print("Time is after start time. Assuming something crashed.")
if getConf("worldborder/start_shrink") == "at":
execTimestamp = time.mktime(datetime.datetime.strptime(getConf("worldborder/time"), "%Y-%m-%d %H:%M").timetuple())
if execTimestamp - time.time() > 0:
say("Waiting for the border to start shrinking at " + getConf("worldborder/time"))
time.sleep(execTimestamp - time.time())
else:
print("The border should have started moving")
elif getConf("worldborder/start_shrink") == "after":
delay = timeToSeconds(getConf("worldborder/time"))
if startTimestamp + delay - time.time() > 0:
say("Waiting for the border to start shrinking after " + getConf("worldborder/time"))
time.sleep(startTimestamp + delay - time.time())
else:
print("The border should have started moving")
with createCon() as mcr:
w = getBorder(mcr)
if w == int(getConf("worldborder/second_size")):
print("The world border has shrinked to it's intended size and UHC-controller's work here is done!")
return
# This compensates (compared to just using worldborder/shrink_time) in case the UHC-controller or the
# server is shut down during the game. It will move faster than it was supposed to but will be at the
# desired width at the asigned time. Instantly moving the worldborder to where it should be would be
# more accurat but might risk some players getting stuck on the outside.
moveTime = startTimestamp + delay + timeToSeconds(getConf("worldborder/shrink_time")) - int(round(time.time()))
s = (w - int(getConf("worldborder/second_size"))) / 2
if moveTime > 0:
v = s / moveTime
say("The border is now moving from " + str(w) + " to " + getConf("worldborder/second_size") + " blocks wide in " + secondsToTime(moveTime) + " (" + str(round(v, 2)) + " blocks per second)", mcr)
# Safe guard so that a border wall won't move faster than a certain speed defined at worldborder/cap_speed.
if getConf("worldborder/cap_speed") is not False:
if v > float(getConf("worldborder/cap_speed")):
moveTime = math.floor(s / float(getConf("worldborder/cap_speed")))
say("However, that's a bit fast. So the speed of a border wall will be caped at " + getConf("worldborder/cap_speed") + " blocks per second. The shrinking will now take " + secondsToTime(moveTime) + " instead")
else:
say("The border should've been at " + getConf("worldborder/second_size") + " blocks wide right now if everything had worked. The border will now shrink from " + str(w) + " + to " + getConf("worldborder/second_size") + " blocks wide at fastest speed allowed (" + getConf("worldborder/cap_speed") + " blocks per second)")
moveTime = math.floor(s / float(getConf("worldborder/cap_speed")))
setBorder(getConf("worldborder/second_size"), math.floor(moveTime))
print("Game loop end. Nothing more to do!")
def argSetup(part, **kwargs):
if part == "properties": propertiesSetup()
if part == "teams": teamsSetup(kwargs.get("config"))
def argStart(*args, **kwargs):
global teams
configFileName = kwargs.get("config")
if configFileName == None:
configFileName = "uhc_config.json"
if os.path.exists(configFileName):
with open(configFileName) as f:
mergeDict(conf, json.load(f))
teamConfFileName = kwargs.get("teams")
if teamConfFileName == None:
teamConfFileName = "uhc_teams.json"
if os.path.exists(teamConfFileName):
with open(teamConfFileName) as f:
teams = json.load(f)
startTime = kwargs.get("start_time")
if startTime is not None:
conf["start_time"] = startTime
spawnPoint = kwargs.get("spawn_point")
if spawnPoint is not None:
x,z = spawnPoint.split(" ")
conf["spawn_point"]["x"] = x
conf["spawn_point"]["z"] = z
if getConf("start_time") is None:
print("'start_time' has to been set")
return
if os.path.exists("server.properties"):
with open("server.properties") as f:
for line in f.readlines():
if line.startswith("#"): continue
parts = line.split("=", 1)
conf["prop"][parts[0]] = parts[1].rstrip()
gameLoop()
argumentTree = {
"name": "UHC-controller",
"subparsers": [
{
"name": "setup",
"cmd": argSetup,
"arguments": [
{
"name": "part",
"choices": ["teams", "properties"]
},
{
"name": "-config"
}
]
},
{
"name": "start",
"cmd": argStart,
"arguments": [
{
"name": "-start_time"
},
{
"name": "-spawn_point"
},
{
"name": "-config"
},
{
"name": "-teams"
}
]
}
]
}
parseArgTree(argumentTree)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment