diff --git a/overviewer_core/observer.py b/overviewer_core/observer.py
index e17c8f5..e068acb 100644
--- a/overviewer_core/observer.py
+++ b/overviewer_core/observer.py
@@ -19,6 +19,7 @@ import progressbar
import sys
import os
import json
+import rcon
class Observer(object):
"""Base class that defines the observer interface.
@@ -392,3 +393,43 @@ class ServerAnnounceObserver(Observer):
self.target_handle.write('say %s\n' % output)
self.target_handle.flush()
+
+# Fair amount of code duplication incoming
+# Perhaps both ServerAnnounceObserver and RConObserver
+# could share the percentage interval code.
+
+class RConObserver(Observer):
+ """Send the output to a Minecraft server via rcon"""
+
+ def __init__(self, target, password, port=25575, pct_interval=10):
+ self.pct_interval = pct_interval
+ self.conn = rcon.RConConnection(target, port)
+ self.conn.login(password)
+ self.last_update = 0
+ super(RConObserver, self).__init__()
+
+ def start(self, max_value):
+ self._send_output("Starting render of %d total tiles" % max_value)
+ super(RConObserver, self).start(max_value)
+
+ def finish(self):
+ self._send_output("Render complete!")
+ super(RConObserver, self).finish()
+ self.conn.close()
+
+ def update(self, current_value):
+ super(RConObserver, self).update(current_value)
+ if self._need_update():
+ self._send_output('Rendered %d of %d tiles, %d%% complete' %
+ (self.get_current_value(), self.get_max_value(),
+ self.get_percentage()))
+ self.last_update = current_value
+
+ def _need_update(self):
+ return self.get_percentage() - \
+ (self.last_update * 100.0 / self.get_max_value()) >= self.pct_interval
+
+ def _send_output(self, output):
+ self.conn.command("say", output)
+
+
diff --git a/overviewer_core/rcon.py b/overviewer_core/rcon.py
new file mode 100644
index 0000000..52a1434
--- /dev/null
+++ b/overviewer_core/rcon.py
@@ -0,0 +1,95 @@
+# This file is part of the Minecraft Overviewer.
+#
+# Minecraft Overviewer is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or (at
+# your option) any later version.
+#
+# Minecraft Overviewer is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with the Overviewer. If not, see .
+
+import socket
+#from enum import Enum
+import struct
+import select
+
+class RConException(Exception):
+ def __init__(self, request_id, reason):
+ self.request_id = request_id
+ self.reason = reason
+
+ def __str__(self):
+ return ("Failed RCon request with request ID %d, reason %s" %
+ (self.request_id, self.reason))
+
+# In D, enums are just that, enums. They're a group of named constants,
+# sometimes with a tag, sometimes anonymous.
+# In Python, Enums use the same syntax as class objects that derive from
+# the "Enum" base class, even though they are not normal python classes
+# and work as singletons anyway, but instead of using a different syntax,
+# Python instead decided to have a chapter in their docs about how Enums
+# are different from regular classes while looking exactly the same.
+# You can look at said document of failure right here:
+# https://docs.python.org/3/library/enum.html#how-are-enums-different
+#
+# "D has too much shit going on for me" -- agrif, 2014
+#
+# Fortunately, we're not allowed to use Enums in Python 2.
+
+#class RConType(Enum):
+# command = 2
+# login = 3
+
+
+class RConConnection():
+ rid = 0
+
+ def __init__(self, target, port):
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((target, port))
+
+ def send(self, t, payload):
+ self.rid = self.rid + 1
+ header = struct.pack("