diff --git a/PCRC.py b/PCRC.py index 3ca043f..23b324a 100644 --- a/PCRC.py +++ b/PCRC.py @@ -6,13 +6,14 @@ import traceback import utils -from Logger import Logger -from Recorder import Recorder, Config +from logger import Logger +from recorder import Recorder +from config import Config from pycraft.compat import input from pycraft.exceptions import YggdrasilError recorder = None -logger = Logger(name='PCRC', file_name=utils.LoggingFileName) +logger = Logger(name='PCRC') ConfigFile = 'config.json' TranslationFolder = 'lang/' @@ -44,7 +45,7 @@ def isWorking(): def stop(): global recorder, logger if isWorking(): - recorder.stop() + recorder.stop(by_user=True) while not recorder.is_stopped(): time.sleep(0.1) logger.log('Recorder stopped') @@ -124,6 +125,8 @@ def main(): except Exception: logger.error(traceback.format_exc()) + logger.log('Exited') + if __name__ == "__main__": on_start_up() diff --git a/ReplayFile.py b/ReplayFile.py deleted file mode 100644 index ba09f98..0000000 --- a/ReplayFile.py +++ /dev/null @@ -1,37 +0,0 @@ -import json -import zipfile - -import utils - - -class ReplayFile: - def __init__(self, file_name=None, tmcpr_name=None, meta_data=None, markers=None, mods=None): - self.file_name = file_name - self.tmcpr_name = tmcpr_name if tmcpr_name is not None else utils.RecordingFileName - self.meta_data = meta_data - self.markers = markers if markers is not None else [] - self.mods = mods if mods is not None else [] - - def set_file_name(self, file_name): - self.file_name = file_name - - def set_tmcpr_name(self, tmcpr_name): - self.tmcpr_name = tmcpr_name - - def set_meta_data(self, meta_data): - self.meta_data = meta_data - - def set_markers(self, markers): - self.markers = markers - - def set_mods(self, mods): - self.mods = mods - - def create(self): - zipf = zipfile.ZipFile(self.file_name, 'w', zipfile.ZIP_DEFLATED) - utils.addFile(zipf, 'markers.json', fileData=json.dumps(self.markers)) - utils.addFile(zipf, 'mods.json', fileData=json.dumps({"requiredMods": self.mods})) - utils.addFile(zipf, 'metaData.json', fileData=json.dumps(self.meta_data)) - utils.addFile(zipf, '{}.crc32'.format(utils.RecordingFileName), fileData=str(utils.crc32_file(self.tmcpr_name))) - utils.addFile(zipf, self.tmcpr_name, arcname=utils.RecordingFileName) - zipf.close() diff --git a/config.json b/config.json index 74f6d07..6e8713c 100644 --- a/config.json +++ b/config.json @@ -16,6 +16,7 @@ "file_buffer_size_mb": 8, "time_recorded_limit_hour": 12, "delay_before_afk_second": 15, + "record_packets_when_afk": true, "upload_file": false, "auto_relogin": true, "chat_spam_protect": true, diff --git a/config.py b/config.py new file mode 100644 index 0000000..73db1da --- /dev/null +++ b/config.py @@ -0,0 +1,132 @@ +import json + + +DefaultOption = json.loads(''' +{ + "__1__": "-------- Base --------", + "language": "en_us", + "debug_mode": false, + + "__2__": "-------- Account and Server --------", + "online_mode": false, + "username": "bot_PCRC", + "password": "secret", + "address": "localhost", + "port": 20000, + "server_name": "SECRET SERVER", + + "__3__": "-------- PCRC Control --------", + "file_size_limit_mb": 512, + "file_buffer_size_mb": 8, + "time_recorded_limit_hour": 12, + "delay_before_afk_second": 15, + "record_packets_when_afk": true, + "upload_file": false, + "auto_relogin": true, + "chat_spam_protect": true, + + "__4__": "-------- PCRC Features --------", + "minimal_packets": true, + "daytime": 4000, + "weather": false, + "with_player_only": true, + "remove_items": false, + "remove_bats": true, + "remove_phantoms": true +} +''') + +SettableOptions = [ + 'language', + 'server_name', + 'minimal_packets', + 'daytime', + 'weather', + 'with_player_only', + 'remove_items', + 'remove_bats' +] + + +class Config: + def __init__(self, file_name): + self.file_name = file_name + try: + with open(file_name) as f: + self.data = json.load(f) + except FileNotFoundError: + self.data = {} + self.fill_missing_options() + self.write_to_file() + + def fill_missing_options(self): + new_data = {} + for key in DefaultOption.keys(): + new_data[key] = self.data.get(key, DefaultOption[key]) + self.data = new_data + + def get_option_type(self, option): + return type(self.data[option]) + + def convert_to_option_type(self, option, value): + t = self.get_option_type(option) + if t == bool: + value = value in ['True', 'true', 'TRUE', True] or (type(value) is int and value != 0) + else: + value = t(value) + return value + + def set_value(self, option, value, forced=False): + if not forced: + value = self.convert_to_option_type(option, value) + self.data[option] = value + return + + def write_to_file(self, file_name=None): + if file_name is None: + file_name = self.file_name + text = json.dumps(self.data, indent=4) + for key in self.data.keys(): + if len(key) == 5 and key[0] == key[1] == key[3] == key[4] == '_' and key != '__1__': + p = text.find(' "{}"'.format(key)) + text = text[:p] + '\n' + text[p:] + with open(file_name, 'w') as f: + f.write(text) + + def get(self, option): + if option in self.data: + return self.data[option] + else: + return None + + def display(self): + def secret(text): + return '******' if len(text) <= 4 else '{}***{}'.format(text[0:2], text[-1]) + messages = [] + messages.append('================ Config ================') + messages.append('-------- Base --------') + messages.append(f"Language = {self.get('language')}") + messages.append(f"Debug mode = {self.get('debug_mode')}") + messages.append('-------- Account and Server --------') + messages.append(f"Online mode = {self.get('online_mode')}") + messages.append(f"User name = {secret(self.get('username'))}") + messages.append(f"Password = ******") + messages.append(f"Server address = {self.get('address')}") + messages.append(f"Server port = {self.get('port')}") + messages.append(f"Server name = {self.get('server_name')}") + messages.append('-------- PCRC Control --------') + messages.append(f"File size limit = {self.get('file_size_limit_mb')}MB") + messages.append(f"File buffer size = {self.get('file_buffer_size_mb')}MB") + messages.append(f"Time recorded limit = {self.get('time_recorded_limit_hour')}h") + messages.append(f"Upload file to transfer.sh = {self.get('upload_file')}") + messages.append(f"Auto relogin = {self.get('auto_relogin')}") + messages.append(f"Chat spam protect = {self.get('chat_spam_protect')}") + messages.append('-------- PCRC Features --------') + messages.append(f"Minimal packets mode = {self.get('minimal_packets')}") + messages.append(f"Daytime set to = {self.get('daytime')}") + messages.append(f"Weather switch = {self.get('weather')}") + messages.append(f"Record with player only = {self.get('with_player_only')}") + messages.append(f"Remove items = {self.get('remove_items')}") + messages.append(f"Remove bats = {self.get('remove_bats')}") + messages.append('========================================') + return '\n'.join(messages) diff --git a/Logger.py b/logger.py similarity index 86% rename from Logger.py rename to logger.py index deb5428..12f5b2a 100644 --- a/Logger.py +++ b/logger.py @@ -3,12 +3,18 @@ class Logger: - def __init__(self, name=None, thread=None, file_name=None, display_debug=False): + DefaultFileName = 'PCRC.log' + + def __init__(self, name=None, thread=None, file_name=DefaultFileName, display_debug=False): self.name = name self.thread = thread self.file_name = file_name self.display_debug = display_debug + @staticmethod + def set_default_file_name(fn): + Logger.DefaultFileName = fn + def _log(self, msg, log_type, do_print): if not isinstance(msg, str): msg = str(msg) diff --git a/pycraft/networking/connection.py b/pycraft/networking/connection.py index c4503dc..d472967 100644 --- a/pycraft/networking/connection.py +++ b/pycraft/networking/connection.py @@ -15,7 +15,7 @@ from future.utils import raise_ -import Logger +import logger from .types import VarInt from .packets import clientbound, serverbound from . import packets diff --git a/readme.md b/readme.md index d36cf5d..5140158 100644 --- a/readme.md +++ b/readme.md @@ -55,7 +55,7 @@ The config file is `config.json`. All settings can be changed in it. Those which `debug_mode`: Whether outputs debug info or not -## Account and Server +### Account and Server `online_mode`: Use online mode to login or offline mode instead @@ -69,7 +69,7 @@ The config file is `config.json`. All settings can be changed in it. Those which `server_name`: The server name showed in replay viewer -## PCRC Control +### PCRC Control `file_size_limit_mb`: The limit of size of the `.tmcpr` file. Every time it is reached, PCRC will restart. Default: `512` @@ -79,6 +79,8 @@ The config file is `config.json`. All settings can be changed in it. Those which `delay_before_afk_second`: The time delay between every player leaving and PCRC pausing recording. Default: `15` +`record_packets_when_afk`: If set to false, PCRC will ignore almost every incoming packets when PCRC pauses recording + `upload_file`: If set to true, .mcpr file will be sent to [transfer.sh](transfer.sh) after finishing recording `auto_relogin`: If this option is enabled and the client gets disconnected, it will automatically try to reconnect diff --git a/readme_cn.md b/readme_cn.md index 1337a88..96a6ddd 100644 --- a/readme_cn.md +++ b/readme_cn.md @@ -79,6 +79,8 @@ PCRC 目前支持连接 `1.12`、`1.12.2` 以及 `1.14.4` 的原版 Minecraft `delay_before_afk_second`: 所有人都离开与暂停录制间的延迟,单位: 秒。默认值: `15` +`record_packets_when_afk`: 若设为 `false`,PCRC 将会在暂停录制时忽略几乎所有到来的数据包 + `upload_file`: 是否将录制好的文件上传至 [transfer.sh](transfer.sh) 以便进行分享~~(国内用户还是关掉吧不然上传十年)~~ `auto_relogin`: 当客户端掉线时是否自动重连。若为 `true`,PCRC 会在掉线后尝试重连 diff --git a/Recorder.py b/recorder.py similarity index 84% rename from Recorder.py rename to recorder.py index 5e60ded..382ab8d 100644 --- a/Recorder.py +++ b/recorder.py @@ -11,10 +11,11 @@ import datetime import subprocess -from ReplayFile import ReplayFile -from Translation import Translation +import config +from replay_file import ReplayFile +from translation import Translation import utils -from Logger import Logger +from logger import Logger from pycraft import authentication from pycraft.networking.connection import Connection from pycraft.networking.packets import Packet as PycraftPacket, clientbound, serverbound @@ -22,88 +23,15 @@ from pycraft.networking.types import PositionAndLook -class Config: - SettableOptions = ['language', 'server_name', 'minimal_packets', 'daytime', 'weather', 'with_player_only', 'remove_items', 'remove_bats'] - def __init__(self, file_name): - self.file_name = file_name - with open(file_name) as f: - self.data = json.load(f) - - def get_option_type(self, option): - return type(self.data[option]) - - def convert_to_option_type(self, option, value): - t = self.get_option_type(option) - if t == bool: - value = value in ['True', 'true', 'TRUE', True] or (type(value) is int and value != 0) - else: - value = t(value) - return value - - def set_value(self, option, value, forced=False): - if not forced: - value = self.convert_to_option_type(option, value) - self.data[option] = value - return - - def write_to_file(self, file_name=None): - if file_name is None: - file_name = self.file_name - text = json.dumps(self.data, indent=4) - for key in self.data.keys(): - if len(key) == 5 and key[0] == key[1] == key[3] == key[4] == '_' and key != '__1__': - p = text.find(' "{}"'.format(key)) - text = text[:p] + '\n' + text[p:] - with open(file_name, 'w') as f: - f.write(text) - - def get(self, option): - if option in self.data: - return self.data[option] - else: - return None - - def display(self): - def secret(text): - return '******' if len(text) <= 4 else '{}***{}'.format(text[0:2], text[-1]) - messages = [] - messages.append('================ Config ================') - messages.append('-------- Base --------') - messages.append(f"Language = {self.get('language')}") - messages.append(f"Debug mode = {self.get('debug_mode')}") - messages.append('-------- Account and Server --------') - messages.append(f"Online mode = {self.get('online_mode')}") - messages.append(f"User name = {secret(self.get('username'))}") - messages.append(f"Password = ******") - messages.append(f"Server address = {self.get('address')}") - messages.append(f"Server port = {self.get('port')}") - messages.append(f"Server name = {self.get('server_name')}") - messages.append('-------- PCRC Control --------') - messages.append(f"File size limit = {self.get('file_size_limit_mb')}MB") - messages.append(f"File buffer size = {self.get('file_buffer_size_mb')}MB") - messages.append(f"Time recorded limit = {self.get('time_recorded_limit_hour')}h") - messages.append(f"Upload file to transfer.sh = {self.get('upload_file')}") - messages.append(f"Auto relogin = {self.get('auto_relogin')}") - messages.append(f"Chat spam protect = {self.get('chat_spam_protect')}") - messages.append('-------- PCRC Features --------') - messages.append(f"Minimal packets mode = {self.get('minimal_packets')}") - messages.append(f"Daytime set to = {self.get('daytime')}") - messages.append(f"Weather switch = {self.get('weather')}") - messages.append(f"Record with player only = {self.get('with_player_only')}") - messages.append(f"Remove items = {self.get('remove_items')}") - messages.append(f"Remove bats = {self.get('remove_bats')}") - messages.append('========================================') - return '\n'.join(messages) - - class Recorder: socket_id = None def __init__(self, config_file, translation_folder): - self.config = Config(config_file) + self.config = config.Config(config_file) self.translations = Translation(translation_folder) self.working = False self.online = False + self.stop_by_user = False # set to true once PCRC is stopped by user; reset to false when PCRC starts self.file_thread = None self.chat_thread = None self.file_buffer = bytearray() @@ -111,7 +39,7 @@ def __init__(self, config_file, translation_folder): self.file_urls = [] self.mc_version = None self.mc_protocol = None - self.logger = Logger(name='Recorder', file_name='PCRC.log', display_debug=self.config.get('debug_mode')) + self.logger = Logger(name='Recorder', display_debug=self.config.get('debug_mode')) self.print_config() allowed_versions = ['1.12', '1.12.2', '1.14.4'] @@ -140,9 +68,6 @@ def __init__(self, config_file, translation_folder): self.protocolMap = {} self.logger.log('init finish') - def __del__(self): - self.stop() - def translation(self, text): return self.translations.translate(text, self.config.get('language')) @@ -160,7 +85,11 @@ def isWorking(self): def onConnectionException(self, exc, exc_info): self.logger.error('Exception in network thread: {}'.format(exc)) self.logger.debug(traceback.format_exc()) - self.stop(restart=self.config.get('auto_relogin')) + if not self.stop_by_user: + self.logger.error('Stopping the recorder since PCRC has not been stopped by user') + self.stop(restart=self.config.get('auto_relogin')) + else: + self.logger.log('Don''t panic, that''s Works As Intended') def onPacketSent(self, packet): self.logger.debug('<- {}'.format(packet.data)) @@ -170,7 +99,7 @@ def onPacketReceived(self, packet): self.processPacketData(packet) def onGameJoin(self, packet): - self.logger.log('PCRC joined to the server') + self.logger.log('PCRC bot joined the server') self.online = True self.chat(self.translation('OnGameJoin')) @@ -300,7 +229,7 @@ def processPacketData(self, packet_raw): # Recording if self.isWorking() and packet_recorded is not None: - if not self.isAFKing() or packet_name in utils.IMPORTANT_PACKETS: + if not self.isAFKing() or packet_name in utils.IMPORTANT_PACKETS or self.config.get('record_packets_when_afk'): bytes_recorded = packet_recorded.read(packet_recorded.remaining()) data = self.timeRecorded().to_bytes(4, byteorder='big', signed=True) data += len(bytes_recorded).to_bytes(4, byteorder='big', signed=True) @@ -317,7 +246,7 @@ def processPacketData(self, packet_raw): self.logger.debug('{} packet ignore'.format(packet_name)) pass - if self.isWorking() and self.file_size > self.file_size_limit(): + if self.isWorking() and self.replay_file.size() > self.file_size_limit(): self.logger.log('tmcpr file size limit {}MB reached! Restarting'.format(utils.convert_file_size_MB(self.file_size_limit()))) self.chat(self.translation('OnReachFileSizeLimit').format(utils.convert_file_size_MB(self.file_size_limit()))) self.restart() @@ -341,11 +270,9 @@ def get_showinfo_time(): def flush(self): if len(self.file_buffer) == 0: return - with open(utils.RecordingFileName, 'ab+') as replay_recording: - replay_recording.write(self.file_buffer) - self.file_size += len(self.file_buffer) - self.logger.log('Flushing {} bytes to "{}" file, file size = {}MB now'.format( - len(self.file_buffer), utils.RecordingFileName, utils.convert_file_size_MB(self.file_size) + self.replay_file.write(self.file_buffer) + self.logger.log('Flushing {} bytes to "recording.tmcpr" file, file size = {}MB now'.format( + len(self.file_buffer), utils.convert_file_size_MB(self.replay_file.size()) )) self.file_buffer = bytearray() @@ -363,6 +290,7 @@ def start(self): if not self.is_stopped(): return self.logger.log('Starting PCRC') + self.stop_by_user = False success = self.connect() if not success: self.stop(restart=self.config.get('auto_relogin')) @@ -386,7 +314,6 @@ def on_protocol_version_decided(self, protocol_version): # initializing stuffs def on_recording_start(self): self.working = True - open(utils.RecordingFileName, 'w').close() self.start_time = utils.getMilliTime() self.last_player_movement = self.start_time self.afk_time = 0 @@ -394,12 +321,11 @@ def on_recording_start(self): self.last_no_player_movement = False self.player_uuids = [] self.file_buffer = bytearray() - self.file_size = 0 self.last_showinfo_time = 0 self.packet_counter = 0 self.last_showinfo_packetcounter = 0 self.file_thread = None - self.markers = [] + self.replay_file = ReplayFile(path=utils.RecordingFilePath) self.pos = None if self.chat_thread is not None: self.chat_thread.kill() @@ -408,17 +334,16 @@ def on_recording_start(self): if 'Time Update' in utils.BAD_PACKETS: utils.BAD_PACKETS.remove('Time Update') - def stop(self, restart=False): - self.logger.log('Stopping PCRC, restart = {}'.format(restart)) + def stop(self, restart=False, by_user=False): + self.logger.log('Stopping PCRC, restart = {}, by_user = {}'.format(restart, by_user)) if self.isOnline(): self.chat(self.translation('OnPCRCStopping')) + if by_user: + self.stop_by_user = True self.working = False self.createReplayFile(restart) return True - def restart(self): - self.stop(restart=True) - def createReplayFile(self, restart): if self.file_thread is not None: return @@ -430,27 +355,26 @@ def _createReplayFile(self, restart): logger = copy.deepcopy(self.logger) logger.thread = 'File' try: - self.__createReplayFile(logger, restart) + self.__createReplayFile(logger) + except AttributeError: + logger.log('Recorder has not started up, aborted creating replay file') + logger.debug(traceback.format_exc()) finally: self.on_final_stop(logger, restart) - def __createReplayFile(self, logger, restart): + def __createReplayFile(self, logger): self.flush() if self.mc_version is None or self.mc_protocol is None: logger.log('Not connected to the server yet, abort creating replay recording file') return - if self.file_size < utils.MinimumLegalFileSize: - logger.warn('Size of "{}" too small ({}KB < {}KB), abort creating replay file'.format( - utils.RecordingFileName, utils.convert_file_size_KB(self.file_size), utils.convert_file_size_KB(utils.MinimumLegalFileSize) + if self.replay_file.size() < utils.MinimumLegalFileSize: + logger.warn('Size of "recording.tmcpr" too small ({}KB < {}KB), abort creating replay file'.format( + utils.convert_file_size_KB(self.replay_file.size()), utils.convert_file_size_KB(utils.MinimumLegalFileSize) )) return - if not os.path.isfile(utils.RecordingFileName): - logger.warn('"{}" file not found, abort creating replay file'.format(utils.RecordingFileName)) - return - # Creating .mcpr zipfile based on timestamp logger.log('Time recorded/passed: {}/{}'.format(utils.convert_millis(self.timeRecorded()), utils.convert_millis(self.timePassed()))) @@ -471,7 +395,7 @@ def __createReplayFile(self, logger, restart): if self.isOnline(): self.chat(self.translation('OnCreatingMCPRFile')) - meta_data = utils.get_meta_data( + self.replay_file.meta_data = utils.get_meta_data( server_name=self.config.get('server_name'), duration=self.timeRecorded(), date=utils.getMilliTime(), @@ -479,8 +403,7 @@ def __createReplayFile(self, logger, restart): protocol=self.mc_protocol, player_uuids=self.player_uuids ) - file = ReplayFile(file_name, utils.RecordingFileName, meta_data, markers=self.markers) - file.create() + self.replay_file.create(file_name) logger.log('Size of replay file "{}": {}MB'.format(file_name, utils.convert_file_size_MB(os.path.getsize(file_name)))) file_path = f'{utils.RecordingStorageFolder}{file_name}' @@ -491,7 +414,7 @@ def __createReplayFile(self, logger, restart): if self.config.get('upload_file'): if self.isOnline(): self.chat(self.translation('OnUploadingMCPRFile')) - logger.log('Uploading "{}" to transfer.sh'.format(utils.RecordingFileName)) + logger.log('Uploading "{}" to transfer.sh'.format(file_name)) try: ret, out = subprocess.getstatusoutput( 'curl --upload-file {} https://transfer.sh/{}'.format(file_path, file_name)) @@ -499,13 +422,20 @@ def __createReplayFile(self, logger, restart): self.file_urls.append(url) if self.isOnline(): self.chat(self.translation('OnUploadedMCPRFile').format(file_name, url), priority=ChatThread.Priority.High) - except Exception as e: - logger.error('Fail to upload "{}" to transfer.sh'.format(utils.RecordingFileName)) + except Exception: + logger.error('Fail to upload "{}" to transfer.sh'.format(file_name)) logger.error(traceback.format_exc()) def on_final_stop(self, logger, restart): logger.log('File operations finished, disconnect now') - self.disconnect() + try: + self.disconnect() + except Exception as e: + logger.warn('Fail to disconnect: {}'.format(e)) + try: + self.connection.disconnect(immediate=True) + except Exception as e: + logger.warn('Fail to immediately disconnect: {}'.format(e)) self.file_thread = None self.mc_version = None self.mc_protocol = None @@ -576,7 +506,7 @@ def format_status(self, text): return text.format( self.isWorking(), self.isWorking() and not self.isAFKing(), utils.convert_millis(self.timeRecorded()), utils.convert_millis(self.timePassed()), - self.packet_counter, utils.convert_file_size_MB(len(self.file_buffer)), utils.convert_file_size_MB(self.file_size), + self.packet_counter, utils.convert_file_size_MB(len(self.file_buffer)), utils.convert_file_size_MB(self.replay_file.size()), self.file_name ) @@ -589,7 +519,7 @@ def print_urls(self): self.chat(url) def set_config(self, option, value, forced=False): - if not forced and option not in Config.SettableOptions: + if not forced and option not in config.SettableOptions: self.chat(self.translation('IllegalOptionName').format(option)) return self.chat(self.translation('OnOptionSet').format(option, value)) @@ -610,31 +540,14 @@ def add_marker(self, name=None): self.logger.warn('Fail to add marker, position unknown!') return time_stamp = self.timeRecorded() - marker = { - 'realTimestamp': time_stamp, - 'value': { - 'position': { - 'x': self.pos.x, - 'y': self.pos.y, - 'z': self.pos.z, - # seems that replay mod switches these two values, idk y - 'yaw': self.pos.pitch, - 'pitch': self.pos.yaw, - 'roll': 0.0 - } - } - } - if name is not None: - marker['value']['name'] = name - self.markers.append(marker) + marker = self.replay_file.add_marker(self.timeRecorded(), self.pos, name) self.chat(self.translation('OnMarkerAdded').format(utils.convert_millis(time_stamp))) - self.logger.log('Marker added: {}, {} markers has been stored'.format(marker, len(self.markers))) + self.logger.log('Marker added: {}, {} markers has been stored'.format(marker, len(self.replay_file.markers))) def delete_marker(self, index): - index -= 1 - marker = self.markers.pop(index) + marker = self.replay_file.markers.pop(index - 1) self.chat(self.translation('OnMarkerDeleted').format(utils.convert_millis(marker['realTimestamp']))) - self.logger.log('Marker deleted: {}, {} markers has been stored'.format(marker, len(self.markers))) + self.logger.log('Marker deleted: {}, {} markers has been stored'.format(marker, len(self.replay_file.markers))) def set_file_name(self, new_name): old_name = self.file_name @@ -663,16 +576,16 @@ def processCommand(self, command, sender, uuid): else: self.chat(self.translation('CommandPositionResultUnknown')) elif len(args) == 2 and args[1] in ['stop']: - self.stop() + self.stop(by_user=True) elif len(args) == 2 and args[1] == 'restart': - self.restart() + self.stop(restart=True, by_user=True) elif len(args) == 2 and args[1] in ['url', 'urls']: self.print_urls() elif len(args) == 4 and args[1] == 'set': self.set_config(args[2], args[3]) elif len(args) == 2 and args[1] == 'set': self.chat(self.translation('CommandSetListTitle')) - self.chat(', '.join(Config.SettableOptions)) + self.chat(', '.join(config.SettableOptions)) elif (len(args) == 2 and args[1] == 'marker') or (len(args) == 3 and args[1] == 'marker' and args[2] == 'list'): self.print_markers() elif 3 <= len(args) <= 4 and args[1] == 'marker' and args[2] == 'add': diff --git a/replay_file.py b/replay_file.py new file mode 100644 index 0000000..655a94e --- /dev/null +++ b/replay_file.py @@ -0,0 +1,63 @@ +import json +import os +import zipfile + +import utils + + +class ReplayFile: + def __init__(self, path='./'): + self.path = path + self.mods = [] + self.meta_data = {} + self.markers = [] + if path[-1] not in ['/', '\\']: + path = path + '/' + if not os.path.exists(path): + os.makedirs(path) + open('{}recording.tmcpr'.format(self.path), 'w').close() + self.file_size = 0 + + def create(self, file_name): + tmcpr = '{}recording.tmcpr'.format(self.path) + if not os.path.isfile(tmcpr): + open(tmcpr, 'w').close() + zipf = zipfile.ZipFile(file_name, 'w', zipfile.ZIP_DEFLATED) + + def add(zipf, name, data=None): + utils.addFile(zipf, '{}{}'.format(self.path, name), arcname=name, fileData=data) + + add(zipf, 'markers.json', json.dumps(self.markers)) + add(zipf, 'mods.json', json.dumps({"requiredMods": self.mods})) + add(zipf, 'metaData.json', json.dumps(self.meta_data)) + add(zipf, 'recording.tmcpr.crc32', str(utils.crc32_file(tmcpr))) + add(zipf, 'recording.tmcpr') + zipf.close() + + def add_marker(self, time_stamp, pos, name=None): + marker = { + 'realTimestamp': time_stamp, + 'value': { + 'position': { + 'x': pos.x, + 'y': pos.y, + 'z': pos.z, + # seems that replay mod switches these two values, idk y + 'yaw': pos.pitch, + 'pitch': pos.yaw, + 'roll': 0.0 + } + } + } + if name is not None: + marker['value']['name'] = name + self.markers.append(marker) + return marker + + def write(self, data): + with open('{}recording.tmcpr'.format(self.path), 'ab+') as replay_recording: + replay_recording.write(data) + self.file_size += len(data) + + def size(self): + return self.file_size diff --git a/Translation.py b/translation.py similarity index 100% rename from Translation.py rename to translation.py diff --git a/utils.py b/utils.py index b045e10..bc62716 100644 --- a/utils.py +++ b/utils.py @@ -5,9 +5,8 @@ import pycraft Version = '0.7-alpha' -RecordingFileName = 'recording.tmcpr' +RecordingFilePath = 'temp_recording/' RecordingStorageFolder = 'PCRC_recordings/' -LoggingFileName = 'PCRC.log' MilliSecondPerHour = 60 * 60 * 1000 BytePerKB = 1024 BytePerMB = BytePerKB * 1024