diff --git a/.gitignore b/.gitignore index d4e4c2f..a7e4d21 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,14 @@ nosetests.xml .pydevproject # Project specific -*config.json +*.json *.db *.db-journal *.log -.idea/ \ No newline at end of file +*.log.* +.idea/ + +# Vim mess cleanup +*.swp +*.un~ +.ropeproject/ diff --git a/.landscape.yaml b/.landscape.yaml new file mode 100644 index 0000000..6a8d7d1 --- /dev/null +++ b/.landscape.yaml @@ -0,0 +1,4 @@ +doc-warnings: yes +test-warnings: no +strictness: high +autodetect: yes \ No newline at end of file diff --git a/README.md b/README.md index da7ed2a..4861d57 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,13 @@ With the built-in plugins (which are removable): * Join/quit announcements. * And more. +## Version 1.5 is here! -## Upgrading from older versions of StarryPy +With this most recent release, we are compatible with the current release of Starbound (Upbeat Giraffe). Any bugs found in the process, please open an issue ticket, so we can squash them as quickly as possible. -In version 1.4.x a new requirement "sqlite3" has been added to requirements.txt. -It is required to perform DB upgrade. Install manually with: pip install sqlite3 +## Upgrading from older versions of StarryPy +StarryPy 1.5 is **NOT** backwards compatible with older versions. As unfortunate as it is, we suggest wiping your database, and starting fresh. It will save a lot of headaches in the long run. ## Installation @@ -116,6 +117,13 @@ variables: on. * passthrough: **Make sure this is false.** It is an emergency off switch for StarryPy. + +In addition you may also want to setup + +* owner_uuid: This should be the UUID associated with whatever in-game character you want to have 'ownership' rights on the server. From there you can promote others to moderator, admin, or even owner as well. +* admin_ss: This is a password that unlocks the ability for Moderators, Admins and Owners to use commands on the server. This is a deadman switch system, meaning that you **will not be able to use privileged commands** if you do not have this password setup. Once you have set a password here, when logging into your server from Starbound, type this password in the `account` field. This is used **in addition** to your server password. +* log_level: This is, but default, set to `DEBUG`. If you want leaner logs, set it to `INFO`. If you want to see all the details going on in the background, set it to `VDEBUG`. +* irc settings: If you want chat replication to an IRC chatroom, setup your bot parameters here. A password is may not be required for all servers, but the added security isn't a bad idea. Finally, find starbound.config and change `gameport` to be exactly the same as `upstream_port` in config.json. @@ -134,7 +142,7 @@ Enter `python server.py` to start the proxy. StarryPy is nearly entirely plugin driven (our plugin manager is a plugin!), so there are quite a few built-in plugins. The truly important plugins are in the -core\_plugins folder. If you remove any of those, it's likely that most other +`core/` plugins folder. If you remove any of those, it's likely that most other plugins will break. We'll break them down by core plugin and normal plugin classes. @@ -180,11 +188,6 @@ prefixed with ## will be sent to moderators+ only. `Access: Everyone` This plugin simply announces whenever a player joins or quits the server. -#### Bouncer - -This plugin prevents non-registered users from building or destroying anything. It -is disabled by default. - #### Colored names This plugin displays color codes for each username depending on rank. The colors @@ -196,14 +199,12 @@ This plugin sends a Message of the Day on login. #### New Player Greeter -Greets first-time players on the server. Gives them a greeting (located in -new\_player\_message.txt) and gives them a pack of starter items (located in -starter\_items.txt). Default items are 200 `coalore` and 5 `alienburger`s. +Greets first-time players on the server. Gives them a greeting that can be configured in the config file and can also give them a pack of starter items. (200 Coal is currently included to give you a sample of the format expected.) #### Planet Protection This plugin protects specified planets against modification in any way. Currently -if a planet is protected only registered users may modify it. +if a planet is protected only those users who are the planet's protect list may make modifications. #### Plugin Manager @@ -212,9 +213,16 @@ that it's a plugin, you don't have to tell me. #### Warpy -This plugin provides various methods for warping players and ships around. +This plugin provides various methods for warping players around. + +#### PoI and Bookmarks + +These are two seperate plugins that do similar things. Every user has the ability to planets to their own personal bookmark list, allowing for fast return to the planet later. + +Points of Interest are setup by administrators for the whole community, allowing fast, fuel-free travel to those desintations. #### WebGUI + If activated, this will give you a web-GUI to administrate your StarryPy server. You can log in with your Character's name and the password you set as "ownerpassword" in the StarryPy config. ##### Config Parameters: @@ -231,11 +239,6 @@ If activated, this will give you a web-GUI to administrate your StarryPy server. * [jQuery-Knob](http://anthonyterrien.com/knob/), [D3](http://d3js.org/), [Select2](https://github.com/ivaynberg/select2), [Bootstrap Validator](https://github.com/nghuuphuoc/bootstrapvalidator), [TinyMCE](http://www.tinymce.com), [jQuery Timepicker](http://trentrichardson.com/examples/timepicker/), [xCharts](http://tenxer.github.io/xcharts/), [Fancybox](http://fancyapps.com/fancybox/), [Widen FineUploader](https://github.com/Widen/fine-uploader), [Datatables](http://datatables.net), jQuery-UI 1.10.4, [Twitter Bootstrap](http://getbootstrap.com), [Flot](www.flotcharts.org), [Fullcalendar](http://arshaw.com/fullcalendar), [Moment](http://momentjs.com/), [Justified Gallery](https://github.com/miromannino/Justified-Gallery), [Morris Charts](http://www.oesmith.co.uk/morris.js/) -#### More plugins - -Even more plugins can be found over at -[our plugin list](https://github.com/MrMarvin/StarryPy_plugins). - ## Plugin development There are several built-in plugins that you can derive inspiration from. The @@ -262,8 +265,9 @@ provide a link. ### None of the commands are working -You are likely in passthrough mode or connecting to the vanilla server. Check -config.json and ensure that passthrough is false. +You likely did not provide the admin_ss password. If the server is responding "You're not logged in, so I can't let you do that", then this is the case. Please check that you have the admin_ss password set, and that you are putting it in the `account` field at login. + +If you are seeing responses from the Starbound built-in commands, then you are either in passthrough mode, connecting directly to the vanilla server, or your account is in a foul state. For the first case, check config.json and ensure that passthrough is false. For the next, ensure you are indeed connecting to to the StarryPy server port, and not the vanilla server's. If you're account is fouled, have another moderator or admin kick you character. This seems to clear the bad sate. If you are running StarryPy on the same computer you're playing it from, it is likely it is using the gameport in starbound.config to connect to. To avoid this, @@ -286,17 +290,9 @@ We have quite a roadmap, here are some of the highlights you can expect in the next major version, and in the development branch before that if you're feeling brave: -* Spawn networks. Free transportation between admin-designated planets, so your - new players can get a leg up in the world. -* Loot rolling. So a rare item dropped and you don't think it's fair your friend - got it? Soon you'll be able to get good items without ending friendships and - going to prison on the inevitable murder charge. -* Lotteries. Because what is life without a little risk? * Creature spawning. Want to spawn a couple dozen bone dragons? So do we! * Internationalization. Translate plugins and core messages with ease to your preferred language. -* Role based access control Thought the mod/admin/owner distinction is useful, - having individual roles is our plan for the future. * Client filtering based on modded items. Though asset digests aren't supported right now, we want to do some minor filtering to keep out the riff-raff (if you as an admin want to.) @@ -311,7 +307,7 @@ issues page. We're absolutely happy to accept pull requests. There is a freenode channel called [##starbound-dev](http://webchat.freenode.net/?channels=##starbound-dev) -that we discuss our development on primarily. +that we discuss our development on primarily. We also have the channel [##starrypy](http://webchat.freenode.net/?channels=##starrypy) for discussion specific to our wrapper. Other than that, please report any bugs you find with the appropriate section of the debug.log file that is generated. diff --git a/base_plugin.py b/base_plugin.py index d981a37..7976995 100644 --- a/base_plugin.py +++ b/base_plugin.py @@ -54,6 +54,9 @@ def on_handshake_challenge(self, data): def on_chat_received(self, data): return True + def on_celestial_request(self, data): + return True + def on_universe_time_update(self, data): return True @@ -144,7 +147,7 @@ def on_burn_container(self, data): def on_clear_container(self, data): return True - def on_world_update(self, data): + def on_world_client_state_update(self, data): return True def on_entity_create(self, data): @@ -156,6 +159,9 @@ def on_entity_update(self, data): def on_entity_destroy(self, data): return True + def on_hit_request(self, data): + return True + def on_status_effect_request(self, data): return True @@ -177,10 +183,16 @@ def on_damage_notification(self, data): def on_client_connect(self, data): return True - def on_client_disconnect(self, player): + def on_client_disconnect_request(self, player): + return True + + def on_player_warp(self, data): + return True + + def on_fly_ship(self, data): return True - def on_warp_command(self, data): + def on_central_structure_update(self, data): return True def after_protocol_version(self, data): @@ -195,6 +207,9 @@ def after_handshake_challenge(self, data): def after_chat_received(self, data): return True + def after_celestial_request(self, data): + return True + def after_universe_time_update(self, data): return True @@ -285,7 +300,7 @@ def after_burn_container(self, data): def after_clear_container(self, data): return True - def after_world_update(self, data): + def after_world_client_state_update(self, data): return True def after_entity_create(self, data): @@ -297,6 +312,9 @@ def after_entity_update(self, data): def after_entity_destroy(self, data): return True + def after_hit_request(self, data): + return True + def after_status_effect_request(self, data): return True @@ -318,10 +336,16 @@ def after_damage_notification(self, data): def after_client_connect(self, data): return True - def after_client_disconnect(self, data): + def after_client_disconnect_request(self, data): + return True + + def after_player_warp(self, data): + return True + + def after_fly_ship(self, data): return True - def after_warp_command(self, data): + def after_central_structure_update(self, data): return True def __repr__(self): diff --git a/config.py b/config.py index 23a3bf8..6a591a9 100644 --- a/config.py +++ b/config.py @@ -60,6 +60,7 @@ def __init__(self): def save(self): try: with io.open(self.config_path.path, "w", encoding="utf-8") as config: + self.logger.debug("Writing configuration file.") config.write(json.dumps(self.config, indent=4, separators=(',', ': '), sort_keys=True, ensure_ascii=False)) except Exception as e: self.logger.critical("Tried to save the configuration file, failed.\n%s", str(e)) diff --git a/config/bookmarks/pois.json b/config/bookmarks/pois.json new file mode 100644 index 0000000..ffa2a82 --- /dev/null +++ b/config/bookmarks/pois.json @@ -0,0 +1 @@ +[["alpha:18199896:-7537982:-29875958:4:0", "spawn"], ["alpha:-46228686:-57338749:-26585271:8:0", "buildplanet"], ["sectorx:1999:1997:-14622445:3:6", "featheropolis"], ["sectorx:-1:-3:5876938:4:0", "pvparena"], ["sectorx:32704560:41511008:-3580117:7:1", "charon"]] diff --git a/config/config.json.default b/config/config.json.default index bb747d8..45f36a6 100644 --- a/config/config.json.default +++ b/config/config.json.default @@ -14,10 +14,7 @@ }, "command_prefix": "/", "core_plugin_path": "core_plugins", - "debug_file": "debug.log", - "logging_format_console": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", - "logging_format_debugfile": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", - "logging_format_logfile": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", + "log_level": "DEBUG", "owner_uuid": "!!--REPLACE THIS--!!", "passthrough": false, "player_db": "config/player.db", @@ -26,8 +23,8 @@ "auto_activate": true }, "afk_plugin": { - "afk_msg": "is now AFK", - "afkreturn_msg": "has returned", + "afk_msg": "^gray;is now AFK.", + "afkreturn_msg": "^gray;has returned.", "auto_activate": true }, "announcer_plugin": { @@ -44,7 +41,8 @@ }, "claims": { "auto_activate": true, - "max_claims": 5 + "max_claims": 5, + "unclaimable_planets": [] }, "colored_names_plugin": { "auto_activate": true @@ -56,12 +54,12 @@ "auto_activate": true }, "fuelgiver_plugin": { - "auto_activate": true + "auto_activate": false }, "irc": { "auto_activate": false, - "bot_nickname": "botname", - "channel": "#channel", + "bot_nickname": "StarryPyBot", + "channel": "##test", "color": "^#e39313;", "echo_from_channel": true, "nickserv_password": "password", @@ -71,6 +69,9 @@ "loginwho_plugin": { "auto_activate": true }, + "mod_chatter": { + "auto_activate": true + }, "motd_plugin": { "auto_activate": true, "motd": "Welcome to the server! Play nice." @@ -79,40 +80,18 @@ "auto_activate": true }, "new_player_greeter_plugin": { - "auto_activate": true, - "items": [ - ], - "message": "Welcome new Starbounder ;)" - }, - "starteritems_plugin": { - "auto_activate": true, + "auto_activate": false, "items": [ - [ "bandage", 20 ], - [ "burger", 20 ], - [ "stoneaxe", 1 ], - [ "stonehoe", 1 ], - [ "stonepickaxe", 1 ], - [ "solariumore", 30 ], - [ "money", 1000 ] + [ + "coalore", + 200 + ] ], - "message": "You were given a set of starter items ;)" + "message": "Welcome to the server!" }, "planet_protect": { "auto_activate": true, - "protect_everything": false, "bad_packets": [ - "CONNECT_WIRE", - "DISCONNECT_ALL_WIRES", - "ENTITY_INTERACT", - "OPEN_CONTAINER", - "CLOSE_CONTAINER", - "SWAP_IN_CONTAINER", - "DAMAGE_TILE", - "DAMAGE_TILE_GROUP", - "REQUEST_DROP", - "MODIFY_TILE_LIST" - ], - "bad_packets_mild": [ "CONNECT_WIRE", "DISCONNECT_ALL_WIRES", "DAMAGE_TILE", @@ -194,28 +173,45 @@ "spinningrocket", "stationaryrocket" ], - "player_planets": {}, - "protected_planets": [] + "player_planets": { + }, + "protect_everything": false, + "protected_planets": [ + ] + }, + "planet_visitor_announcer_plugin": { + "auto_activate": true }, "player_manager": { + "admin_ss": "tester", "auto_activate": true, "name_removal_regexes": [ "\\^#[\\w]+;", "[^ \\w]+" ] }, - "planet_visitor_announcer_plugin": { - "auto_activate": true - }, "players_plugin": { "auto_activate": true }, "plugin_manager": { "auto_activate": true }, + "poi_plugin": { + "auto_activate": true + }, "starbound_config_manager": { "auto_activate": true }, + "starteritems_plugin": { + "auto_activate": false, + "message": "Enjoy these gifts from us!", + "items": [ + [ + "coalore", + 200 + ] + ] + }, "uptime_plugin": { "auto_activate": true }, @@ -230,7 +226,7 @@ "cookie_token": "", "log_path": "webgui.log", "log_path_access": "webgui_access.log", - "ownerpassword": "!!PUT A PASSWORD HERE", + "ownerpassword": "--ADD PASSWORD--", "port": 8083, "remember_cookie_token": true, "restart_script": "" diff --git a/config/pois.json b/config/pois.json new file mode 100644 index 0000000..afec3fd --- /dev/null +++ b/config/pois.json @@ -0,0 +1 @@ +[["-30562840:658406569:-263745435:4:0", "forest"], ["-30562840:658406569:-263745435:3:6", "spawn"]] \ No newline at end of file diff --git a/packet_stream.py b/packet_stream.py index 4b0f4ee..687c097 100644 --- a/packet_stream.py +++ b/packet_stream.py @@ -71,13 +71,17 @@ def check_packet(self): z = zlib.decompressobj() p_parsed.data = z.decompress(p_parsed.data) except zlib.error: - self.logger.error("Decompression error in check_packet.") + self.logger.debug("Parsed packet:") + self.logger.debug(pprint.pformat(p_parsed)) + self.logger.debug("Packet data:") + self.logger.debug(pprint.pformat(p_parsed.original_data.encode("hex"))) + self.logger.debug("Following packet data:") + self.logger.debug(pprint.pformat(self._stream.encode("hex"))) raise packet = Packet(packet_id=p_parsed.id, payload_size=p_parsed.payload_size, data=p_parsed.data, original_data=p, direction=self.direction) - self.compressed = False self.protocol.string_received(packet) self.reset() if self.start_packet(): @@ -90,4 +94,4 @@ def reset(self): self.id = None self.payload_size = None self.packet_size = None - self.compressed = False \ No newline at end of file + self.compressed = False diff --git a/packets/data_types.py b/packets/data_types.py index 676301d..0d819c4 100644 --- a/packets/data_types.py +++ b/packets/data_types.py @@ -1,6 +1,6 @@ import logging from construct import Construct, Struct, Byte, BFloat64, Flag, \ - String, Container + String, Container, Field from construct.core import _read_stream, _write_stream, Adapter @@ -75,10 +75,10 @@ def _encode(self, obj, context): return obj def _decode(self, obj, context): return "".join(obj) + star_string_struct = lambda name="star_string": Struct(name, VLQ("length"), - String("string", lambda ctx: ctx.length) -) + String("string", lambda ctx: ctx.length)) class VariantVariant(Construct): def _parse(self, stream, context): @@ -95,6 +95,35 @@ def _parse(self, stream, context): c[key] = value return c +class WarpVariant(Construct): +# Not all variants have been properly treated! + def _parse(self, stream, context): + x = Byte("").parse_stream(stream) + if x == 0: + return None + elif x == 1: + return star_string().parse_stream(stream) + elif x == 2: + return None + elif x == 3: + flag = Flag("").parse_stream(stream) + return Field("", 16).parse_stream(stream).encode("hex") + elif x == 4: + return star_string().parse_stream(stream) + def _build(self, obj, stream, context): + if len(obj) == 32: + _write_stream(stream, 1, chr(3)) + _write_stream(stream, 1, chr(1)) + _write_stream(stream, len(obj.decode("hex")), obj.decode("hex")) + return + elif obj is "outpost": + _write_stream(stream, 1, chr(1)) + star_string()._build(obj, stream, context) + return + elif obj is None: + _write_stream(stream, 1, chr(4)) + _write_stream(stream, 1, chr(0)) + return class Variant(Construct): def _parse(self, stream, context): diff --git a/packets/packet_types.py b/packets/packet_types.py index 6723602..934fa0a 100644 --- a/packets/packet_types.py +++ b/packets/packet_types.py @@ -1,6 +1,7 @@ from construct import * from enum import IntEnum -from data_types import SignedVLQ, VLQ, Variant, star_string, DictVariant, StarByteArray +from data_types import SignedVLQ, VLQ, Variant, star_string, DictVariant, StarByteArray +from data_types import WarpVariant class Direction(IntEnum): @@ -10,54 +11,59 @@ class Direction(IntEnum): class Packets(IntEnum): PROTOCOL_VERSION = 0 - CONNECT_RESPONSE = 1 - SERVER_DISCONNECT = 2 + SERVER_DISCONNECT = 1 + CONNECT_RESPONSE = 2 HANDSHAKE_CHALLENGE = 3 CHAT_RECEIVED = 4 UNIVERSE_TIME_UPDATE = 5 - CELESTIALRESPONSE = 6 + CELESTIAL_RESPONSE = 6 CLIENT_CONNECT = 7 - CLIENT_DISCONNECT = 8 + CLIENT_DISCONNECT_REQUEST = 8 HANDSHAKE_RESPONSE = 9 - WARP_COMMAND = 10 - CHAT_SENT = 11 - CELESTIALREQUEST = 12 - CLIENT_CONTEXT_UPDATE = 13 - WORLD_START = 14 - WORLD_STOP = 15 - TILE_ARRAY_UPDATE = 16 - TILE_UPDATE = 17 - TILE_LIQUID_UPDATE = 18 - TILE_DAMAGE_UPDATE = 19 - TILE_MODIFICATION_FAILURE = 20 - GIVE_ITEM = 21 - SWAP_IN_CONTAINER_RESULT = 22 - ENVIRONMENT_UPDATE = 23 - ENTITY_INTERACT_RESULT = 24 - MODIFY_TILE_LIST = 25 - DAMAGE_TILE = 26 - DAMAGE_TILE_GROUP = 27 - REQUEST_DROP = 28 - SPAWN_ENTITY = 29 - ENTITY_INTERACT = 30 - CONNECT_WIRE = 31 - DISCONNECT_ALL_WIRES = 32 - OPEN_CONTAINER = 33 - CLOSE_CONTAINER = 34 - SWAP_IN_CONTAINER = 35 - ITEM_APPLY_IN_CONTAINER = 36 - START_CRAFTING_IN_CONTAINER = 37 - STOP_CRAFTING_IN_CONTAINER = 38 - BURN_CONTAINER = 39 - CLEAR_CONTAINER = 40 - WORLD_UPDATE = 41 - ENTITY_CREATE = 42 - ENTITY_UPDATE = 43 - ENTITY_DESTROY = 44 - DAMAGE_NOTIFICATION = 45 - STATUS_EFFECT_REQUEST = 46 - UPDATE_WORLD_PROPERTIES = 47 - HEARTBEAT = 48 + PLAYER_WARP = 10 + FLY_SHIP = 11 + CHAT_SENT = 12 + CELESTIAL_REQUEST = 13 + CLIENT_CONTEXT_UPDATE = 14 + WORLD_START = 15 + WORLD_STOP = 16 + CENTRAL_STRUCTURE_UPDATE = 17 + TILE_ARRAY_UPDATE = 18 + TILE_UPDATE = 19 + TILE_LIQUID_UPDATE = 20 + TILE_DAMAGE_UPDATE = 21 + TILE_MODIFICATION_FAILURE = 22 + GIVE_ITEM = 23 + SWAP_IN_CONTAINER_RESULT = 24 + ENVIRONMENT_UPDATE = 25 + ENTITY_INTERACT_RESULT = 26 + UPDATE_TILE_PROTECTION = 27 + MODIFY_TILE_LIST = 28 + DAMAGE_TILE_GROUP = 29 + COLLECT_LIQUID = 30 + REQUEST_DROP = 31 + SPAWN_ENTITY = 32 + ENTITY_INTERACT = 33 + CONNECT_WIRE = 34 + DISCONNECT_ALL_WIRES = 35 + OPEN_CONTAINER = 36 + CLOSE_CONTAINER = 37 + SWAP_IN_CONTAINER = 38 + ITEM_APPLY_IN_CONTAINER = 39 + START_CRAFTING_IN_CONTAINER = 40 + STOP_CRAFTING_IN_CONTAINER = 41 + BURN_CONTAINER = 42 + CLEAR_CONTAINER = 43 + WORLD_CLIENT_STATE_UPDATE = 44 + ENTITY_CREATE = 45 + ENTITY_UPDATE = 46 + ENTITY_DESTROY = 47 + HIT_REQUEST = 48 + DAMAGE_REQUEST = 49 + DAMAGE_NOTIFICATION = 50 + CALL_SCRIPTED_ENTITY = 51 + UPDATE_WORLD_PROPERTIES = 52 + HEARTBEAT = 53 class EntityType(IntEnum): @@ -70,6 +76,23 @@ class EntityType(IntEnum): PLANT = 5 PLANTDROP = 6 EFFECT = 7 + NPC = 8 + + +class InteractionType(IntEnum): + NONE = 0 + OPEN_COCKPIT_INTERFACE = 1 + OPEN_CONTAINER = 2 + SIT_DOWN = 3 + OPEN_CRAFTING_INTERFACE = 4 + PLAY_CINEMATIC = 5 + OPEN_SONGBOOK_INTERFACE = 6 + OPEN_NPC_CRAFTING_INTERFACE = 7 + OPEN_NPC_BOUNTY_INTERFACE = 8 + OPEN_AI_INTERFACE = 9 + OPEN_TELEPORT_DIALOG = 10 + SHOW_POPUP = 11 + SCRIPT_CONSOLE = 12 class PacketOutOfOrder(Exception): @@ -84,13 +107,7 @@ def _encode(self, obj, context): def _decode(self, obj, context): return obj.encode("hex") - -handshake_response = lambda name="handshake_response": Struct(name, - star_string("claim_response"), - star_string("hash")) - -universe_time_update = lambda name="universe_time": Struct(name, - VLQ("unknown")) +# ---------- Utility constructs --------- packet = lambda name="base_packet": Struct(name, Byte("id"), @@ -102,108 +119,274 @@ def _decode(self, obj, context): Byte("id"), SignedVLQ("payload_size")) -protocol_version = lambda name="protocol_version": Struct(name, - UBInt32("server_build")) - connection = lambda name="connection": Struct(name, GreedyRange(Byte("compressed_data"))) -handshake_challenge = lambda name="handshake_challenge": Struct(name, - star_string("claim_message"), - star_string("salt"), - SBInt32("round_count")) +celestial_coordinate = lambda name="celestial_coordinate": Struct(name, + SBInt32("x"), + SBInt32("y"), + SBInt32("z"), + SBInt32("planet"), + SBInt32("satellite")) -connect_response = lambda name="connect_response": Struct(name, - Flag("success"), - VLQ("client_id"), - star_string("reject_reason")) +projectile = DictVariant("projectile") -chat_received = lambda name="chat_received": Struct(name, - Byte("chat_channel"), - star_string("world"), - UBInt32("client_id"), - star_string("name"), - star_string("message")) +# --------------------------------------- -chat_sent = lambda name="chat_sent": Struct(name, - star_string("message"), - Padding(1)) +# ----- Primary connection sequence ----- + +# (0) - ProtocolVersion : S -> C +protocol_version = lambda name="protocol_version": Struct(name, + UBInt32("server_build")) +# (7) - ClientConnect : C -> S client_connect = lambda name="client_connect": Struct(name, VLQ("asset_digest_length"), String("asset_digest", lambda ctx: ctx.asset_digest_length), - Variant("claim"), Flag("uuid_exists"), If(lambda ctx: ctx.uuid_exists is True, HexAdapter(Field("uuid", 16)) ), star_string("name"), star_string("species"), - VLQ("shipworld_length"), - Field("shipworld", lambda ctx: ctx.shipworld_length), + StarByteArray("ship_data"), + UBInt32("ship_level"), + UBInt32("max_fuel"), + VLQ("capabilities_length"), + Array(lambda ctx: ctx.capabilities_length, + Struct("capabilities", + star_string("value"))), star_string("account")) -client_disconnect = lambda name="client_disconnect": Struct(name, - Byte("data")) - -world_coordinate = lambda name="world_coordinate": Struct(name, - star_string("sector"), - SBInt32("x"), - SBInt32("y"), - SBInt32("z"), - SBInt32("planet"), - SBInt32("satellite")) - -warp_command = lambda name="warp_command": Struct(name, - Enum(UBInt32("warp_type"), - MOVE_SHIP=1, - WARP_UP=2, - WARP_OTHER_SHIP=3, - WARP_DOWN=4, - WARP_HOME=5), - world_coordinate(), - star_string("player")) - -warp_command_write = lambda t, sector=u'', x=0, y=0, z=0, planet=0, satellite=0, player=u'': warp_command().build( +# (3) - HandshakeChallenge : S -> C +handshake_challenge = lambda name="handshake_challenge": Struct(name, + StarByteArray("salt")) + +# (9) - HandshakeResponse : C -> S +handshake_response = lambda name="handshake_response": Struct(name, + star_string("hash")) + +# (2) - ConnectResponse : S -> C +connect_response = lambda name="connect_response": Struct(name, + Flag("success"), + VLQ("client_id"), + star_string("reject_reason"), + Flag("celestial_info_exists"), + If(lambda ctx: ctx.celestial_info_exists, + Struct( + "celestial_data", + SBInt32("planet_orbital_levels"), + SBInt32("satellite_orbital_levels"), + SBInt32("chunk_size"), + SBInt32("xy_min"), + SBInt32("xy_max"), + SBInt32("z_min"), + SBInt32("z_max")))) + +# --------------------------------------- + +# (1) - ServerDisconnect +server_disconnect = lambda name="server_disconnect": Struct(name, + star_string("reason")) + +# (5) - UniverseTimeUpdate +universe_time_update = lambda name="universe_time": Struct(name, + BFloat64("universe_time")) + +# (8) - ClientDisconnectRequest +client_disconnect_request = lambda name="client_disconnect_request": Struct(name, + Byte("data")) + +# (4) - ChatReceived +chat_received = lambda name="chat_received": Struct(name, + Enum(Byte("mode"), + CHANNEL=0, + BROADCAST=1, + WHISPER=2, + COMMAND_RESULT=3), + star_string("channel"), + UBInt32("client_id"), + star_string("name"), + star_string("message")) + +# (12) - ChatSent +chat_sent = lambda name="chat_sent": Struct(name, + star_string("message"), + Enum(Byte("send_mode"), + BROADCAST=0, + LOCAL=1, + PARTY=2) + ) + +chat_sent_write = lambda message, send_mode: chat_sent().build( + Container( + message=message, + send_mode=send_mode)) + +# (10) - PlayerWarp +player_warp = lambda name="player_warp": Struct(name, + Enum(UBInt8("warp_type"), + WARP_TO=0, + WARP_RETURN=1, + WARP_TO_HOME_WORLD=2, + WARP_TO_ORBITED_WORLD=3, + WARP_TO_OWN_SHIP=4), + WarpVariant("world_id")) + +player_warp_write = lambda t, world_id: player_warp().build( Container( warp_type=t, - world_coordinate=Container( - sector=sector, + world_id=world_id)) + +# (11) - FlyShip +fly_ship = lambda name="fly_ship": Struct(name, + celestial_coordinate()) + +fly_ship_write = lambda x=0, y=0, z=0, planet=0, satellite=0: fly_ship().build( + Container( + celestial_coordinate=Container( x=x, y=y, z=z, planet=planet, - satellite=satellite - ), - player=player)) + satellite=satellite))) + +# (13) - CelestialRequest +celestial_request = lambda name="celestial_request": Struct(name, + GreedyRange(star_string("requests"))) +# (14) - ClientContextUpdate +client_context_update = lambda name="client_context": Struct(name, + VLQ("length"), + Byte("arguments"), + Array(lambda ctx: ctx.arguments, + Struct("key", + Variant("value")))) +# (15) - WorldStart world_start = lambda name="world_start": Struct(name, Variant("planet"), - Variant("world_structure"), - StarByteArray("sky_structure"), + StarByteArray("sky_data"), StarByteArray("weather_data"), - BFloat32("spawn_x"), - BFloat32("spawn_y"), + # Dungeon ID stuff here + BFloat32("x"), + BFloat32("y"), Variant("world_properties"), UBInt32("client_id"), Flag("local_interpolation")) +# (16) - WorldStop world_stop = lambda name="world_stop": Struct(name, star_string("status")) +# (17) - CentralStructureUpdate +central_structure_update = lambda name="central_structure_update": Struct(name, + Variant("structureData")) + +# (23) - GiveItem give_item = lambda name="give_item": Struct(name, star_string("name"), VLQ("count"), Byte("variant_type"), star_string("description")) -give_item_write = lambda name, count: give_item().build(Container(name=name, - count=count, - variant_type=7, - description='')) - +give_item_write = lambda name, count: give_item().build( + Container( + name=name, + count=count, + variant_type=7, + description='')) + +# (38) - SwapInContainer +swap_in_container = lambda name="swap_in_container": Struct(name, + VLQ("entity_id"), # Where are we putting stuff + star_string("item_name"), + VLQ("count"), + Byte("variant_type"), + StarByteArray("item_description"), + VLQ("slot")) + +# (24) - SwapInContainerResult - aka what item is selected / in our hand (does +# not mean wielding) +swap_in_container_result = lambda name="swap_in_container_result": Struct(name, + star_string("item_name"), + VLQ("count"), + Byte("variant_type"), + GreedyRange(StarByteArray("item_description"))) + +# (34) - SpawnEntity +spawn_entity = lambda name="spawn_entity": Struct(name, + GreedyRange( + Struct("entity", + Byte("entity_type"), + VLQ("payload_size"), + String("payload", lambda ctx: ctx.payload_size)))) + +# (45) - EntityCreate +entity_create = lambda name="entity_create": Struct(name, + GreedyRange( + Struct("entity", + Byte("entity_type"), + VLQ("payload_size"), + String("payload", lambda ctx: ctx.payload_size), + VLQ("entity_id")))) + +# (46) - EntityUpdate +entity_update = lambda name="entity_update": Struct(name, + UBInt32("entity_id"), + StarByteArray("delta")) + +# (47) - EntityDestroy +entity_destroy = lambda name="entity_destroy": Struct(name, + UBInt32("entity_id"), + Flag("death")) + +# (30) - EntityInteract +entity_interact = lambda name="entity_interact": Struct(name, + UBInt32("source_entity_id"), + BFloat32("source_x"), + BFloat32("source_y"), + UBInt32("target_entity_id")) + +# (26) - EntityInteractResult +entity_interact_result = lambda name="entity_interact_result": Struct(name, + UBInt32("interaction_type"), + UBInt32("target_entity_id"), + Variant("entity_data")) + +# (48) - HitRequest +hit_request = lambda name="hit_request": Struct(name, + UBInt32("source_entity_id"), + UBInt32("target_entity_id")) + +# (DamageRequest) - DamageRequest +damage_request = lambda name="damage_request": Struct(name, + UBInt32("source_entity_id"), + UBInt32("target_entity_id"), + UBint8("hit_type"), + UBInt8("damage_type"), + BFloat32("damage"), + BFloat32("knockback_x"), + BFloat32("knockback_y"), + UBInt32("source_entity_id_wut"), + star_string("damage_source_kind"), + GreedyReange(star_string("stuats_effects")) + ) + +# (50) - DamageNotification +damage_notification = lambda name="damage_notification": Struct(name, + UBInt32("source_entity_id"), + UBInt32("source_entity_id_wut"), + UBInt32("target_entity_id"), + VLQ("x"), + VLQ("y"), + VLQ("damage"), + star_string("damage_kind"), + star_string("target_material"), + Flag("killed")) + +# (52) - UpdateWorldProperties update_world_properties = lambda name="world_properties": Struct(name, UBInt8("count"), Array(lambda ctx: ctx.count, @@ -216,12 +399,6 @@ def _decode(self, obj, context): count=len(dictionary), properties=[Container(key=k, value=Container(type="SVLQ", data=v)) for k, v in dictionary.items()])) -entity_create = Struct("entity_create", - GreedyRange( - Struct("entity", - Byte("entity_type"), - VLQ("entity_size"), - String("entity", lambda ctx: ctx.entity_size), - SignedVLQ("entity_id") - ))) -projectile = DictVariant("projectile") \ No newline at end of file +# (53) - Heartbeat +heartbeat = lambda name="heartbeat": Structure(name, + UBInt64("remote_step")) \ No newline at end of file diff --git a/plugin_manager.py b/plugin_manager.py index fab9f00..000b186 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -203,7 +203,7 @@ def wrapped_function(self, data): res = self.plugin_manager.do(self, on, data) if res: res = func(self, data) - d = deferLater(reactor, 1, self.plugin_manager.do, self, after, data) + d = deferLater(reactor, .01, self.plugin_manager.do, self, after, data) d.addErrback(print_this_defered_failure) return res diff --git a/plugins/admin_messenger/admin_messenger.py b/plugins/admin_messenger/admin_messenger.py index bf43d14..42b7339 100644 --- a/plugins/admin_messenger/admin_messenger.py +++ b/plugins/admin_messenger/admin_messenger.py @@ -5,7 +5,7 @@ class AdminMessenger(BasePlugin): - """Adds support to message moderators/admins/owner with a ## prefixed message.""" + """Adds support to message moderators/admins/owner with a @@ prefixed message.""" name = "admin_messenger" depends = ['player_manager'] auto_activate = True @@ -33,8 +33,8 @@ def message_admins(self, message): for protocol in self.factory.protocols.itervalues(): if protocol.player.access_level >= UserLevels.MODERATOR: protocol.send_chat_message(timestamp + - "%sADMIN: ^yellow;<%s^yellow;> %s%s" % (self.config.colors["admin"], self.protocol.player.colored_name(self.config.colors), - self.config.colors["admin"],message.message[2:].decode("utf-8"))) + "%sADMIN: ^yellow;<%s^yellow;> %s%s" % (self.config.colors["moderator"], self.protocol.player.colored_name(self.config.colors), + self.config.colors["moderator"],message.message[2:].decode("utf-8"))) self.logger.info("Received an admin message from %s. Message: %s", self.protocol.player.name, message.message[2:].decode("utf-8")) diff --git a/plugins/afk_plugin/afk_plugin.py b/plugins/afk_plugin/afk_plugin.py index eaa3d6a..dfbe61d 100644 --- a/plugins/afk_plugin/afk_plugin.py +++ b/plugins/afk_plugin/afk_plugin.py @@ -26,7 +26,7 @@ def load_config(self): self.afk_message = self.config.plugin_config["afk_msg"] self.afkreturn_message = self.config.plugin_config["afkreturn_msg"] except Exception as e: - self.logger.info("Error occured! %s" % e) + self.logger.error("Error occured! %s", e) if self.protocol is not None: self.protocol.send_chat_message("Reload failed! Please check config.json!") self.protocol.send_chat_message("Initiating with default values...") diff --git a/plugins/announcer_plugin/announcer_plugin.py b/plugins/announcer_plugin/announcer_plugin.py index a3838fc..6a325fc 100644 --- a/plugins/announcer_plugin/announcer_plugin.py +++ b/plugins/announcer_plugin/announcer_plugin.py @@ -17,15 +17,14 @@ def after_connect_response(self, data): c = connect_response().parse(data.data) if c.success: self.factory.broadcast( - self.protocol.player.colored_name(self.config.colors) + " logged in.", 0, "", "Announcer") + self.protocol.player.colored_name(self.config.colors) + " logged in.", 0, "Announcer") except AttributeError: return except: self.logger.exception("Unknown error in after_connect_response.") return - def on_client_disconnect(self, data): + def on_client_disconnect_request(self, data): if self.protocol.player is not None: self.factory.broadcast(self.protocol.player.colored_name(self.config.colors) + " logged out.", 0, - "", "Announcer") - + "Announcer") \ No newline at end of file diff --git a/plugins/bookmarks_plugin/__init__.py b/plugins/bookmarks_plugin/__init__.py new file mode 100644 index 0000000..1c97db5 --- /dev/null +++ b/plugins/bookmarks_plugin/__init__.py @@ -0,0 +1 @@ +from bookmarks_plugin import Bookmarks \ No newline at end of file diff --git a/plugins/bookmarks_plugin/bookmarks_plugin.py b/plugins/bookmarks_plugin/bookmarks_plugin.py new file mode 100644 index 0000000..c2a7989 --- /dev/null +++ b/plugins/bookmarks_plugin/bookmarks_plugin.py @@ -0,0 +1,150 @@ +import os +import errno +import json +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from packets import Packets, fly_ship, fly_ship_write +from utility_functions import build_packet + + +class Bookmarks(SimpleCommandPlugin): + """ + Plugin that allows defining planets as personal bookmarks you can /goto to. + """ + name = "bookmarks_plugin" + depends = ['command_dispatcher', 'player_manager'] + commands = ["bookmark_add", "bookmark_del", "goto"] + auto_activate = True + + def activate(self): + super(Bookmarks, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + self.verify_path("./config/bookmarks") + + @permissions(UserLevels.GUEST) + def bookmark_add(self, name): + """Bookmarks a planet for fast warp routes.\nSyntax: /bookmark_add (name)""" + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename) as f: + self.bookmarks = json.load(f) + except: + self.bookmarks = [] + + name = " ".join(name).strip().strip("\t") + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + + if on_ship: + self.protocol.send_chat_message("You need to be on a planet!") + return + if len(name) == 0: + warps = [] + for warp in self.bookmarks: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;,^yellow; ".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.bookmark_add.__doc__) + self.protocol.send_chat_message("Please, provide a valid bookmark name!\nBookmarks: ^yellow;" + warpnames) + return + + for warp in self.bookmarks: + if warp[0] == planet: + self.protocol.send_chat_message("The planet you're on is already bookmarked: ^yellow;" + warp[1]) + return + if warp[1] == name: + self.protocol.send_chat_message("Bookmark with that name already exists!") + return + self.bookmarks.append([planet, name]) + self.protocol.send_chat_message("Bookmark ^yellow;%s^green; added." % name) + self.savebms() + + @permissions(UserLevels.GUEST) + def bookmark_del(self, name): + """Removes current planet from bookmarks.\nSyntax: /bookmark_del (name)""" + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename) as f: + self.bookmarks = json.load(f) + except: + self.bookmarks = [] + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + warps = [] + for warp in self.bookmarks: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;,^yellow; ".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.bookmark_del.__doc__) + self.protocol.send_chat_message("Please, provide a valid bookmark name!\nBookmarks: ^yellow;" + warpnames) + return + + for warp in self.bookmarks: + if warp[1] == name: + self.bookmarks.remove(warp) + self.protocol.send_chat_message("Bookmark ^yellow;%s^green; removed." % name) + self.savebms() + return + self.protocol.send_chat_message("There is no bookmark named: ^yellow;%s" % name) + + @permissions(UserLevels.GUEST) + def goto(self, name): + """Warps your ship to a previously bookmarked planet.\nSyntax: /goto [name] *omit name for a list of bookmarks""" + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename) as f: + self.bookmarks = json.load(f) + except: + self.bookmarks = [] + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + warps = [] + for warp in self.bookmarks: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;,^yellow; ".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.goto.__doc__) + self.protocol.send_chat_message("Bookmarks: ^yellow;" + warpnames) + return + + on_ship = self.protocol.player.on_ship + if not on_ship: + self.protocol.send_chat_message("You need to be on a ship!") + return + + for warp in self.bookmarks: + if warp[1] == name: + x, y, z, planet, satellite = warp[0].split(":") + x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) + warp_packet = build_packet(Packets.FLY_SHIP, + fly_ship_write(x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % name) + return + self.protocol.send_chat_message("There is no bookmark named: ^yellow;%s" % name) + + def savebms(self): + filename = "./config/bookmarks/" + self.protocol.player.uuid + ".json" + try: + with open(filename, "w") as f: + json.dump(self.bookmarks, f) + except: + self.logger.exception("Couldn't save bookmarks.") + raise + + def verify_path(self, path): + """ + Helper function to make sure path exists, and create if it doesn't. + """ + try: + os.makedirs(path) + except OSError as exception: + if exception.errno != errno.EEXIST: + raise diff --git a/plugins/brutus_whisper/brutus_whisper.py b/plugins/brutus_whisper/brutus_whisper.py index c29b497..8759d5f 100644 --- a/plugins/brutus_whisper/brutus_whisper.py +++ b/plugins/brutus_whisper/brutus_whisper.py @@ -109,15 +109,15 @@ def ss(self, data): try: if not self.sspy_enabled_dict[self.protocol.player.name]: self.sspy_enabled_dict[self.protocol.player.name] = True - self.protocol.send_chat_message("SocialSpy has been ^yellow;enabled^green;!") + self.protocol.send_chat_message("SocialSpy has been ^green;enabled^yellow;!") else: self.sspy_enabled_dict[self.protocol.player.name] = False - self.protocol.send_chat_message("SocialSpy has been ^red;disabled^green;!") + self.protocol.send_chat_message("SocialSpy has been ^red;disabled^yellow;!") except: if len(data) != 0 and " ".join(data).lower() in ["on", "true"]: self.sspy_enabled_dict[self.protocol.player.name] = True - self.protocol.send_chat_message("SocialSpy has been ^yellow;enabled^green;!") + self.protocol.send_chat_message("SocialSpy has been ^green;enabled^yellow;!") else: self.sspy_enabled_dict[self.protocol.player.name] = False self.protocol.send_chat_message(self.ss.__doc__) - self.protocol.send_chat_message("SocialSpy is ^red;disabled^green;!") + self.protocol.send_chat_message("SocialSpy is ^red;disabled^yellow;!") \ No newline at end of file diff --git a/plugins/chat_logger/chat_logger.py b/plugins/chat_logger/chat_logger.py index 756abcb..d46f57a 100644 --- a/plugins/chat_logger/chat_logger.py +++ b/plugins/chat_logger/chat_logger.py @@ -3,9 +3,12 @@ class ChatLogger(BasePlugin): + """ + Plugin which parses player chatter into the log file. + """ name = "chat_logger" def on_chat_sent(self, data): parsed = chat_sent().parse(data.data) parsed.message = parsed.message.decode("utf-8") - self.logger.info("Chat message sent: <%s> %s", self.protocol.player.name, parsed.message) \ No newline at end of file + self.logger.info("Chat message sent: <%s> %s", self.protocol.player.name, parsed.message) diff --git a/plugins/claims/claims_plugin.py b/plugins/claims/claims_plugin.py index 84811de..30f7d6a 100644 --- a/plugins/claims/claims_plugin.py +++ b/plugins/claims/claims_plugin.py @@ -1,4 +1,3 @@ -import re from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import UserLevels, permissions from utility_functions import extract_name @@ -6,9 +5,9 @@ class ClaimsPlugin(SimpleCommandPlugin): """ -Allows planets to be either protector or unprotected. On protected planets, -only admins can build. Planets are unprotected by default. -""" + Allows planets to be either protector or unprotected. On protected planets, + only admins can build. Planets are unprotected by default. + """ name = "claims" description = "Claims planets." commands = ["claim", "unclaim", "claim_list", "unclaimable"] @@ -30,7 +29,6 @@ def activate(self): self.protected_planets = self.config.config['plugin_config']['planet_protect']['protected_planets'] self.player_planets = self.config.config['plugin_config']['planet_protect']['player_planets'] self.player_manager = self.plugins["player_manager"].player_manager - self.regexes = self.plugins['player_manager'].regexes @permissions(UserLevels.ADMIN) def unclaimable(self, data): @@ -236,4 +234,4 @@ def save(self): self.config.config['plugin_config']['planet_protect']['player_planets'] = self.player_planets self.config.plugin_config['max_claims'] = self.max_claims self.config.plugin_config['unclaimable_planets'] = self.unclaimable_planets - self.config.save() # save config file + self.config.save() # save config file \ No newline at end of file diff --git a/plugins/core/admin_commands_plugin/admin_command_plugin.py b/plugins/core/admin_commands_plugin/admin_command_plugin.py index 423ddca..f51d07b 100644 --- a/plugins/core/admin_commands_plugin/admin_command_plugin.py +++ b/plugins/core/admin_commands_plugin/admin_command_plugin.py @@ -13,9 +13,8 @@ class UserCommandPlugin(SimpleCommandPlugin): """ name = "user_management_commands" depends = ['command_dispatcher', 'player_manager'] - commands = ["who", "whoami", "whois", "promote", "kick", "ban", "ban_list", "unban", "item", "planet", "mute", - "unmute", - "passthrough", "shutdown", "timestamps"] + commands = ["who", "whoami", "whois", "promote", "kick", "ban", "ban_list", "unban", "item", + "planet", "mute", "unmute", "passthrough", "shutdown", "timestamps"] auto_activate = True def activate(self): @@ -52,7 +51,7 @@ def whois(self, data): if len(data) == 0: self.protocol.send_chat_message(self.whois.__doc__) return - name = " ".join(data) + name, garbage = extract_name(data) info = self.player_manager.whois(name) if info and self.protocol.player.access_level >= UserLevels.ADMIN: self.protocol.send_chat_message( @@ -71,11 +70,18 @@ def whois(self, data): @permissions(UserLevels.MODERATOR) def promote(self, data): """Promotes/demotes a player to a specific rank.\nSyntax: /promote (player) (rank) (where rank is either: guest, registered, moderator, admin, or owner)""" + self.logger.debug("Promote command received with the following data: %s" % ":".join(data)) if len(data) > 0: name = " ".join(data[:-1]) + self.logger.debug("Extracted the name %s in promote command." % name) rank = data[-1].lower() + self.logger.debug("Extracted the rank %s in the promote command." % rank) player = self.player_manager.get_by_name(name) + self.logger.debug("Player object in promote command, found by name, is %s." % str(player)) if player is not None: + self.logger.debug("Player object was not None. Dump of player object follows.") + for line in pprint.pformat(player).split("\n"): + self.logger.debug("\t" + line) old_rank = player.access_level players = self.player_manager.all() if old_rank == 1000: @@ -87,6 +93,8 @@ def promote(self, data): self.protocol.send_chat_message("You are the only (or last) owner. Promote denied!") return if old_rank >= self.protocol.player.access_level and not self.protocol.player.access_level != UserLevels.ADMIN: + self.logger.debug( + "The old rank was greater or equal to the current rank. Sending a message and returning.") self.protocol.send_chat_message( "You cannot change that user's access level as they are at least at an equal level as you.") return @@ -101,12 +109,15 @@ def promote(self, data): elif rank == "guest": self.make_guest(player) else: + self.logger.debug("Non-existent rank. Returning with a help message.") self.protocol.send_chat_message("No such rank!\n" + self.promote.__doc__) return + self.logger.debug("Sending promotion message to promoter.") self.protocol.send_chat_message("%s: %s -> %s" % ( player.colored_name(self.config.colors), UserLevels(old_rank), rank.upper())) + self.logger.debug("Sending promotion message to promoted player.") try: self.factory.protocols[player.protocol].send_chat_message( "%s has promoted you to %s" % ( @@ -114,25 +125,31 @@ def promote(self, data): except KeyError: self.logger.info("Promoted player is not logged in.") else: + self.logger.debug("Player wasn't found. Sending chat message to player.") self.protocol.send_chat_message("Player not found!\n" + self.promote.__doc__) return else: + self.logger.debug("Received blank promotion command. Sending help message.") self.protocol.send_chat_message(self.promote.__doc__) @permissions(UserLevels.MODERATOR) def make_guest(self, player): + self.logger.debug("Setting %s to GUEST", player.name) player.access_level = UserLevels.GUEST @permissions(UserLevels.MODERATOR) def make_registered(self, player): + self.logger.debug("Setting %s to REGISTERED", player.name) player.access_level = UserLevels.REGISTERED @permissions(UserLevels.ADMIN) def make_mod(self, player): player.access_level = UserLevels.MODERATOR + self.logger.debug("Setting %s to MODERATOR", player.name) @permissions(UserLevels.OWNER) def make_admin(self, player): + self.logger.debug("Setting %s to ADMIN", player.name) player.access_level = UserLevels.ADMIN @permissions(UserLevels.OWNER) @@ -148,14 +165,16 @@ def kick(self, data): name, reason = extract_name(data) if not reason: reason = ["no reason given"] + else: + reason = " ".join(reason) info = self.player_manager.whois(name) if info and info.logged_in: self.factory.broadcast("%s^green; kicked %s ^green;(reason: ^yellow;%s^green;)" % (self.protocol.player.colored_name(self.config.colors), info.colored_name(self.config.colors), - " ".join(reason))) + "".join(reason))) self.logger.info("%s kicked %s (reason: %s)", self.protocol.player.name, info.name, - " ".join(reason)) + "".join(reason)) tp = self.factory.protocols[info.protocol] tp.die() else: @@ -171,6 +190,8 @@ def ban(self, data): try: ip = data[0] socket.inet_aton(ip) + print socket.inet_aton(ip) + self.logger.debug("Banning IP address %s" % ip) self.player_manager.ban(ip) self.protocol.send_chat_message("Banned IP: ^red;%s^green;" % ip) self.logger.warning("%s banned IP: %s", self.protocol.player.name, ip) @@ -279,7 +300,7 @@ def item(self, data): @permissions(UserLevels.MODERATOR) def mute(self, data): """Mute a player.\nSyntax: /mute (player)""" - name = " ".join(data) + name, garbage = extract_name(data) player = self.player_manager.get_logged_in_by_name(name) if player is None: self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;" % name) @@ -293,7 +314,7 @@ def mute(self, data): @permissions(UserLevels.MODERATOR) def unmute(self, data): """Unmute a currently muted player.\nSyntax: /unmute (player)""" - name = " ".join(data) + name, garbage = extract_name(data) player = self.player_manager.get_logged_in_by_name(name) if player is None: self.protocol.send_chat_message("Couldn't find a user by the name ^yellow;%s^green;" % name) @@ -312,6 +333,9 @@ def passthrough(self, data): @permissions(UserLevels.OWNER) def shutdown(self, data): """Shutdown the server in n seconds.\nSyntax: /shutdown (seconds) (>0)""" + if len(data) == 0: + self.protocol.send_chat_message(self.shutdown.__doc__) + return try: x = float(data[0]) except ValueError: @@ -345,6 +369,4 @@ def on_chat_sent(self, data): "You are currently ^red;muted^green; and cannot speak. You are limited to commands and admin chat (prefix your lines with ^yellow;%s^green; for admin chat." % ( self.config.chat_prefix * 2)) return False - return True - - + return True \ No newline at end of file diff --git a/plugins/core/player_manager/manager.py b/plugins/core/player_manager/manager.py index 4348164..0c525e4 100644 --- a/plugins/core/player_manager/manager.py +++ b/plugins/core/player_manager/manager.py @@ -18,14 +18,11 @@ @contextmanager def _autoclosing_session(sm): session = sm() - try: yield session - except: session.rollback() raise - finally: session.close() @@ -87,7 +84,6 @@ def as_dict(self): class Banned(Exception): pass - class _UserLevels(object): ranks = dict( GUEST = 0, @@ -106,7 +102,6 @@ def __getattr__(self, item): else: return super(_UserLevels, self).__getattribute__(item) - UserLevels = _UserLevels() @@ -167,6 +162,7 @@ class Player(Base): last_seen = Column(DateTime) access_level = Column(Integer) logged_in = Column(Boolean) + admin_logged_in = Column(Boolean) protocol = Column(String) client_id = Column(Integer) ip = Column(String) @@ -178,8 +174,13 @@ class Player(Base): ips = relationship("IPAddress", order_by="IPAddress.id", backref="players") def colored_name(self, colors): + logger.vdebug("Building colored name.") color = colors[UserLevels(self.access_level).lower()] + logger.vdebug("Color is %s", color) name = self.name + logger.vdebug("Name is %s", name) + logger.vdebug("Returning the following data for colored name. %s:%s:%s", + color, name, colors['default']) return color + name + colors["default"] @property @@ -222,13 +223,14 @@ class Ban(Base): class PlayerManager(object): def __init__(self, config): self.config = config - migrate_db(self.config) + logger.info("Loading player database.") self.engine = create_engine('sqlite:///%s' % path.preauthChild(self.config.player_db).path) Base.metadata.create_all(self.engine) self.sessionmaker = sessionmaker(bind=self.engine, autoflush=True) with _autoclosing_session(self.sessionmaker) as session: - for player in session.query(Player).all(): + for player in session.query(Player).filter_by(logged_in=True).all(): player.logged_in = False + player.admin_logged_in = False player.protocol = None session.commit() @@ -247,7 +249,7 @@ def _cache_and_return_from_session(self, session, record, collection=False): return to_return - def fetch_or_create(self, uuid, name, org_name, ip, protocol=None): + def fetch_or_create(self, uuid, name, org_name, admin_logged_in, ip, protocol=None): with _autoclosing_session(self.sessionmaker) as session: if session.query(Player).filter_by(uuid=uuid, logged_in=True).first(): raise AlreadyLoggedIn @@ -260,30 +262,24 @@ def fetch_or_create(self, uuid, name, org_name, ip, protocol=None): name).uuid != self.get_by_org_name(org_name).uuid): logger.info("Got a duplicate nickname, affixing _ to name") name += "_" - player = session.query(Player).filter_by(uuid=uuid).first() if player: if player.name != name: logger.info("Detected username change.") player.name = name - self.protocol.player.name = name - #name = str(player.name) - #csp = data_parser.ChatSent.build(dict(message="/nick %s" % name, - # channel=0)) - #asyncio.Task(protocol.client_raw_write(pparser.build_packet - # 'chat_sent'], csp))) - #player.protocol.transport.write(build_packet(Packets.CHAT_RECEIVED, chat_received().build(p))) if ip not in player.ips: player.ips.append(IPAddress(ip=ip)) player.ip = ip player.protocol = protocol player.last_seen = datetime.datetime.now() + player.admin_logged_in = admin_logged_in else: logger.info("Adding new player with name: %s" % name) player = Player(uuid=uuid, name=name, org_name=org_name, last_seen=datetime.datetime.now(), access_level=int(UserLevels.GUEST), logged_in=False, + admin_logged_in=False, protocol=protocol, client_id=-1, ip=ip, @@ -407,9 +403,15 @@ def wrapper(f): @wraps(f) def wrapped_function(self, *args, **kwargs): if self.protocol.player.access_level >= level: - return f(self, *args, **kwargs) + if level >= UserLevels.MODERATOR: + if self.protocol.player.admin_logged_in == 0: + self.protocol.send_chat_message("^red;You're not logged in, so I can't let you do that.^yellow;") + else: + return f(self, *args, **kwargs) + else: + return f(self, *args, **kwargs) else: - self.protocol.send_chat_message("You are not an admin.") + self.protocol.send_chat_message("You are not authorized to do this.") return False return wrapped_function diff --git a/plugins/core/player_manager/plugin.py b/plugins/core/player_manager/plugin.py index 1e6be48..2e84db3 100644 --- a/plugins/core/player_manager/plugin.py +++ b/plugins/core/player_manager/plugin.py @@ -14,7 +14,7 @@ class PlayerManagerPlugin(SimpleCommandPlugin): name = "player_manager" - commands = ["player_list", "player_del", "nick", "nick_set"] + commands = ["player_list", "player_delete", "nick", "nick_set"] def activate(self): super(PlayerManagerPlugin, self).activate() @@ -22,6 +22,7 @@ def activate(self): self.l_call = LoopingCall(self.check_logged_in) self.l_call.start(1, now=False) self.regexes = self.config.plugin_config['name_removal_regexes'] + self.adminss = self.config.plugin_config['admin_ss'] def deactivate(self): del self.player_manager @@ -30,6 +31,7 @@ def check_logged_in(self): for player in self.player_manager.who(): if player.protocol not in self.factory.protocols.keys(): player.logged_in = False + player.admin_logged_in = False def on_client_connect(self, data): client_data = client_connect().parse(data.data) @@ -53,17 +55,23 @@ def on_client_connect(self, data): duplicate_player = self.player_manager.get_by_org_name(client_data.name) if duplicate_player is not None and duplicate_player.uuid != client_data.uuid: raise NameError( - "The name of this character is already taken on the server!\nPlease, create a new character with a different name or use Starcheat and change the name.") + "The name of this character is already taken on the server!\nPlease, create a new character with a different name or talk to an administrator.") self.logger.info("Got a duplicate original player name, asking player to change character name!") #rnd_append = str(randrange(10, 99)) #original_name += rnd_append #client_data.name += rnd_append + if client_data.account == self.adminss: + admin_login = True + else: + admin_login = False + original_name = client_data.name client_data.name = changed_name self.protocol.player = self.player_manager.fetch_or_create( name=client_data.name, org_name=original_name, + admin_logged_in = admin_login, uuid=str(client_data.uuid), ip=self.protocol.transport.getPeer().host, protocol=self.protocol.id, @@ -82,6 +90,7 @@ def on_client_connect(self, data): self.reject_with_reason(str(e)) def reject_with_reason(self, reason): + # here there be magic... ask Carrots or Teihoo about this... magic_sector = "AQAAAAwAAAAy+gofAAX14QD/Z2mAAJiWgAUFYWxwaGEMQWxwaGEgU2VjdG9yAAAAELIfhbMFQWxwaGEHAgt0aHJlYXRMZXZlbAYCBAIEAg51bmxvY2tlZEJpb21lcwYHBQRhcmlkBQZkZXNlcnQFBmZvcmVzdAUEc25vdwUEbW9vbgUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkBwcCaWQFBWFscGhhBG5hbWUFDEFscGhhIFNlY3RvcgpzZWN0b3JTZWVkBISWofyWZgxzZWN0b3JTeW1ib2wFFy9jZWxlc3RpYWwvc2VjdG9yLzEucG5nCGh1ZVNoaWZ0BDsGcHJlZml4BQVBbHBoYQ93b3JsZFBhcmFtZXRlcnMHAgt0aHJlYXRMZXZlbAYCBAIEAg51bmxvY2tlZEJpb21lcwYHBQRhcmlkBQZkZXNlcnQFBmZvcmVzdAUEc25vdwUEbW9vbgUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkBGJldGELQmV0YSBTZWN0b3IAAADUWh1fvwRCZXRhBwILdGhyZWF0TGV2ZWwGAgQEBAQOdW5sb2NrZWRCaW9tZXMGCQUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZAcHAmlkBQRiZXRhBG5hbWUFC0JldGEgU2VjdG9yCnNlY3RvclNlZWQEtYuh6v5+DHNlY3RvclN5bWJvbAUXL2NlbGVzdGlhbC9zZWN0b3IvMi5wbmcIaHVlU2hpZnQEAAZwcmVmaXgFBEJldGEPd29ybGRQYXJhbWV0ZXJzBwILdGhyZWF0TGV2ZWwGAgQEBAQOdW5sb2NrZWRCaW9tZXMGCQUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZAVnYW1tYQxHYW1tYSBTZWN0b3IAAADMTMw79wVHYW1tYQcCC3RocmVhdExldmVsBgIEBgQGDnVubG9ja2VkQmlvbWVzBgoFBGFyaWQFBmRlc2VydAUIc2F2YW5uYWgFBmZvcmVzdAUEc25vdwUEbW9vbgUGanVuZ2xlBQpncmFzc2xhbmRzBQZiYXJyZW4FDWFzdGVyb2lkZmllbGQHBwJpZAUFZ2FtbWEEbmFtZQUMR2FtbWEgU2VjdG9yCnNlY3RvclNlZWQEs4nM4e9uDHNlY3RvclN5bWJvbAUXL2NlbGVzdGlhbC9zZWN0b3IvMy5wbmcIaHVlU2hpZnQEPAZwcmVmaXgFBUdhbW1hD3dvcmxkUGFyYW1ldGVycwcCC3RocmVhdExldmVsBgIEBgQGDnVubG9ja2VkQmlvbWVzBgoFBGFyaWQFBmRlc2VydAUIc2F2YW5uYWgFBmZvcmVzdAUEc25vdwUEbW9vbgUGanVuZ2xlBQpncmFzc2xhbmRzBQZiYXJyZW4FDWFzdGVyb2lkZmllbGQFZGVsdGEMRGVsdGEgU2VjdG9yAAAA1Ooj2GcFRGVsdGEHAgt0aHJlYXRMZXZlbAYCBAgECA51bmxvY2tlZEJpb21lcwYOBQRhcmlkBQZkZXNlcnQFCHNhdmFubmFoBQZmb3Jlc3QFBHNub3cFBG1vb24FBmp1bmdsZQUKZ3Jhc3NsYW5kcwUFbWFnbWEFCXRlbnRhY2xlcwUGdHVuZHJhBQh2b2xjYW5pYwUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkBwcCaWQFBWRlbHRhBG5hbWUFDERlbHRhIFNlY3RvcgpzZWN0b3JTZWVkBLWdop7hTgxzZWN0b3JTeW1ib2wFFy9jZWxlc3RpYWwvc2VjdG9yLzQucG5nCGh1ZVNoaWZ0BHgGcHJlZml4BQVEZWx0YQ93b3JsZFBhcmFtZXRlcnMHAgt0aHJlYXRMZXZlbAYCBAgECA51bmxvY2tlZEJpb21lcwYOBQRhcmlkBQZkZXNlcnQFCHNhdmFubmFoBQZmb3Jlc3QFBHNub3cFBG1vb24FBmp1bmdsZQUKZ3Jhc3NsYW5kcwUFbWFnbWEFCXRlbnRhY2xlcwUGdHVuZHJhBQh2b2xjYW5pYwUGYmFycmVuBQ1hc3Rlcm9pZGZpZWxkB3NlY3RvcngIWCBTZWN0b3IAAABjhzJHNwFYBwILdGhyZWF0TGV2ZWwGAgQKBBQOdW5sb2NrZWRCaW9tZXMGDgUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFCmdyYXNzbGFuZHMFBW1hZ21hBQl0ZW50YWNsZXMFBnR1bmRyYQUIdm9sY2FuaWMFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZAcIAmlkBQdzZWN0b3J4BG5hbWUFCFggU2VjdG9yCnNlY3RvclNlZWQEmPDzkpxuDHNlY3RvclN5bWJvbAUXL2NlbGVzdGlhbC9zZWN0b3IveC5wbmcIaHVlU2hpZnQEgTQIcHZwRm9yY2UDAQZwcmVmaXgFAVgPd29ybGRQYXJhbWV0ZXJzBwILdGhyZWF0TGV2ZWwGAgQKBBQOdW5sb2NrZWRCaW9tZXMGDgUEYXJpZAUGZGVzZXJ0BQhzYXZhbm5haAUGZm9yZXN0BQRzbm93BQRtb29uBQZqdW5nbGUFCmdyYXNzbGFuZHMFBW1hZ21hBQl0ZW50YWNsZXMFBnR1bmRyYQUIdm9sY2FuaWMFBmJhcnJlbgUNYXN0ZXJvaWRmaWVsZA==" unlocked_sector_magic = base64.decodestring(magic_sector.encode("ascii")) rejection = build_packet( @@ -115,24 +124,27 @@ def on_connect_response(self, data): def after_world_start(self, data): world_start = packets.world_start().parse(data.data) - if 'fuel.max' in world_start['world_properties']: + if 'ship.maxFuel' in world_start['world_properties']: self.logger.info("Player %s is now on a ship.", self.protocol.player.name) self.protocol.player.on_ship = True self.protocol.player.planet = "On ship" + elif world_start.planet['celestialParameters'] is None: + self.protocol.player.on_ship = False + self.protocol.player.planet = "On Outpost" else: coords = world_start.planet['celestialParameters']['coordinate'] parent_system = coords - location = parent_system['location'] - l = location + l = parent_system['location'] self.protocol.player.on_ship = False - planet = Planet(parent_system['sector'], l[0], l[1], l[2], + planet = Planet(l[0], l[1], l[2], coords['planet'], coords['satellite']) self.protocol.player.planet = str(planet) - def on_client_disconnect(self, player): + def on_client_disconnect_request(self, player): if self.protocol.player is not None and self.protocol.player.logged_in: self.logger.info("Player disconnected: %s", self.protocol.player.name) self.protocol.player.logged_in = False + self.protocol.player.admin_logged_in = False return True @permissions(UserLevels.REGISTERED) @@ -199,7 +211,7 @@ def nick_set(self, data): old_name, player.colored_name(self.config.colors))) @permissions(UserLevels.ADMIN) - def player_del(self, data): + def player_delete(self, data): """Delete a player from database.\nSyntax: /player_del (player)""" if len(data) == 0: self.protocol.send_chat_message(self.player_del.__doc__) @@ -230,16 +242,18 @@ def player_list(self, data): def format_player_response(self, players): if len(players) <= 25: self.protocol.send_chat_message( + #_("Results: %s") % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players])) "Results:\n%s" % "\n".join( ["^cyan;%s: ^yellow;%s ^green;: ^gray;%s" % ( player.uuid, player.colored_name(self.config.colors), player.org_name) for player in players])) else: self.protocol.send_chat_message( + #_("Results: %s)" % "\n".join(["%s: %s" % (player.uuid, player.name) for player in players[:25]]))) "Results:\n%s" % "\n".join( ["^cyan;%s: ^yellow;%s ^green;: ^gray;%s" % ( player.uuid, player.colored_name(self.config.colors), player.org_name) for player in players[:25]])) self.protocol.send_chat_message( "And %d more. Narrow it down with SQL like syntax. Feel free to use a *, it will be replaced appropriately." % ( - len(players) - 25)) + len(players) - 25)) \ No newline at end of file diff --git a/plugins/core/starbound_config_manager/starbound_config_manager.py b/plugins/core/starbound_config_manager/starbound_config_manager.py index eac0ede..4cef27e 100644 --- a/plugins/core/starbound_config_manager/starbound_config_manager.py +++ b/plugins/core/starbound_config_manager/starbound_config_manager.py @@ -2,13 +2,11 @@ from twisted.python.filepath import FilePath from base_plugin import SimpleCommandPlugin from plugin_manager import FatalPluginError -from plugins.core import permissions, UserLevels class StarboundConfigManager(SimpleCommandPlugin): name = "starbound_config_manager" - depends = ['command_dispatcher', 'warpy_plugin'] - commands = ["spawn"] + depends = ['command_dispatcher'] def activate(self): super(StarboundConfigManager, self).activate() @@ -26,18 +24,7 @@ def activate(self): raise FatalPluginError( "Could not parse the starbound configuration file as JSON. Error given from JSON decoder: %s" % str( e)) - if self.config.upstream_port != starbound_config['gamePort']: + if self.config.upstream_port != starbound_config['gameServerPort']: raise FatalPluginError( - "The starbound gamePort option (%d) does not match the config.json upstream_port (%d)." % ( - starbound_config['gamePort'], self.config.upstream_port)) - self._spawn = starbound_config['defaultWorldCoordinate'].split(":") - - @permissions(UserLevels.GUEST) - def spawn(self, data): - """Warps your ship to spawn.\nSyntax: /spawn""" - on_ship = self.protocol.player.on_ship - if not on_ship: - self.protocol.send_chat_message("You need to be on a ship!") - return - self.plugins['warpy_plugin'].move_player_ship(self.protocol, [x for x in self._spawn]) - self.protocol.send_chat_message("Moving your ship to spawn.") \ No newline at end of file + "The starbound gameServerPort option (%d) does not match the config.json upstream_port (%d)." % ( + starbound_config['gameServerPort'], self.config.upstream_port)) \ No newline at end of file diff --git a/plugins/fuelgiver/fuelgiver_plugin.py b/plugins/fuelgiver/fuelgiver_plugin.py index 161b8d4..e5b9e92 100644 --- a/plugins/fuelgiver/fuelgiver_plugin.py +++ b/plugins/fuelgiver/fuelgiver_plugin.py @@ -7,7 +7,7 @@ class FuelGiver(SimpleCommandPlugin): """ - Welcomes new players by giving them fuel to leave the spawn world. + Courteously give players fuel once a day (for those who ask for it). """ name = "fuelgiver_plugin" depends = ["command_dispatcher", "player_manager"] @@ -24,7 +24,7 @@ def fuel(self, data): try: my_storage = self.protocol.player.storage except AttributeError: -# self.logger.debug("Tried to give item to non-existent protocol.") + self.logger.warning("Tried to give item to non-existent protocol.") return if not 'last_given_fuel' in my_storage or float(my_storage['last_given_fuel']) <= float(time()) - 86400: my_storage['last_given_fuel'] = str(time()) @@ -33,5 +33,4 @@ def fuel(self, data): self.protocol.send_chat_message("You were given a daily fuel supply! Now go explore ;)") self.logger.info("Gave fuel to %s.", self.protocol.player.name) else: - self.protocol.send_chat_message("^red;No... -.- Go mining!") - \ No newline at end of file + self.protocol.send_chat_message("^red;No... -.- Go mining!") \ No newline at end of file diff --git a/plugins/irc_plugin/irc_manager.py b/plugins/irc_plugin/irc_manager.py index 1f1b024..e545cc1 100644 --- a/plugins/irc_plugin/irc_manager.py +++ b/plugins/irc_plugin/irc_manager.py @@ -1,11 +1,9 @@ -# twisted imports from twisted.words.protocols import irc from twisted.internet import reactor, protocol from uuid import uuid4 class StarryPyIrcBot(irc.IRCClient): - def __init__(self, logger, nickname, nickserv_password, factory, broadcast_target, colors, echo_from_channel): self.logger = logger self.nickname = nickname @@ -16,23 +14,35 @@ def __init__(self, logger, nickname, nickserv_password, factory, broadcast_targe self.colors = colors self.echo_from_channel = echo_from_channel + def connectionMade(self): + irc.IRCClient.connectionMade(self) + self.logger.info("IRC connection made") + + def connectionLost(self, reason): + irc.IRCClient.connectionLost(self, reason) + self.logger.info("IRC connection lost: %s", format(reason)) + def signedOn(self): + """Called when bot has successfully signed on to server.""" if self.nickserv_password: self.msg("NickServ", "identify %s" % self.nickserv_password) if self.factory.target.startswith("#"): self.join(self.factory.target) else: self.send_greeting(self.factory.target) - - self.logger.info("Connected to IRC") + self.logger.info("Signed into IRC") def joined(self, target): + """This will get called when the bot joins the channel.""" + self.logger.debug("Sucesssfully joined the IRC channel %s.", self.factory.target) self.send_greeting(target) def send_greeting(self, target): + """IRC channel greeting function""" self.msg(target, "%s is live!" % self.nickname) def privmsg(self, user, target, msg): + """This will get called when the bot receives a message.""" user = user.split('!', 1)[0] self.logger.info("IRC Message <%s>: %s" % (user, msg)) if self.echo_from_channel: @@ -45,18 +55,31 @@ def privmsg(self, user, target, msg): )) def action(self, user, target, msg): + """This will get called when a user performs an action in the channel""" user = user.split('!', 1)[0] self.logger.info("IRC Action: %s %s" % (user, msg)) + def irc_NICK(self, prefix, params): + """Called when an IRC user changes their nickname.""" + old_nick = prefix.split('!')[0] + new_nick = params[0] + self.logger.info("%s is now known as %s" % (old_nick, new_nick)) + + +class StarryPyIrcBotFactory(protocol.ReconnectingClientFactory): + """Factory for IRC bot.""" + + # Parameters used in the auto-reconnect system. Currently hard-coded. Will + # eventually move them to the config file. + maxRetries = 100 + initalDelay = 1.0 -class StarryPyIrcBotFactory(protocol.ClientFactory): def __init__(self, target, logger, nickname, nickserv_password, broadcast_target, colors, echo_from_channel): self.nickname = nickname try: self.nickserv_password = nickserv_password except AttributeError: self.nickserv_password = None - self.target = target self.broadcast_target = broadcast_target self.colors = colors @@ -64,13 +87,20 @@ def __init__(self, target, logger, nickname, nickserv_password, broadcast_target self.irc_clients = {} self.echo_from_channel = echo_from_channel + def startedConnecting(self, connector): + self.logger.debug("Factory attempting to connect...") + def buildProtocol(self, addr): irc_client = StarryPyIrcBot(self.logger, self.nickname, self.nickserv_password, self, self.broadcast_target, self.colors, self.echo_from_channel) + irc_client.factory = self self.irc_clients[irc_client.id] = irc_client + self.resetDelay() return irc_client def clientConnectionLost(self, connector, reason): - connector.connect() + self.logger.error("IRC connection lost, reconnecting") + protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason) def clientConnectionFailed(self, connector, reason): - self.logger.error("connection failed: %s" % reason) + self.logger.error("IRC connection failed: %s" % format(reason)) + protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) \ No newline at end of file diff --git a/plugins/irc_plugin/irc_plugin.py b/plugins/irc_plugin/irc_plugin.py index bca877a..99cf1e2 100644 --- a/plugins/irc_plugin/irc_plugin.py +++ b/plugins/irc_plugin/irc_plugin.py @@ -47,6 +47,8 @@ def deactivate(self): def on_chat_sent(self, data): parsed = chat_sent().parse(data.data) + if parsed.send_mode == 'LOCAL': + return True if not parsed.message.startswith('/'): for p in self.irc_factory.irc_clients.itervalues(): p.msg(self.channel, "<%s> %s" % (self.protocol.player.name.encode("utf-8"), parsed.message.encode("utf-8"))) @@ -55,13 +57,11 @@ def on_chat_sent(self, data): def on_client_connect(self, data): parsed = client_connect().parse(data.data) self.logger.info(parsed.name) - for p in self.irc_factory.irc_clients.itervalues(): p.msg(self.channel, "%s connected" % parsed.name.encode("utf-8")) - return True - def on_client_disconnect(self, data): + def on_client_disconnect_request(self, data): if self.protocol.player is not None: for p in self.irc_factory.irc_clients.itervalues(): p.msg(self.channel, "%s disconnected" % self.protocol.player.name.encode("utf-8")) diff --git a/plugins/mod_chatter/__init__.py b/plugins/mod_chatter/__init__.py new file mode 100644 index 0000000..4719056 --- /dev/null +++ b/plugins/mod_chatter/__init__.py @@ -0,0 +1 @@ +from mod_chatter import ModChatter diff --git a/plugins/mod_chatter/mod_chatter.py b/plugins/mod_chatter/mod_chatter.py new file mode 100644 index 0000000..1240534 --- /dev/null +++ b/plugins/mod_chatter/mod_chatter.py @@ -0,0 +1,46 @@ +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +import packets +from datetime import datetime + + +class ModChatter(SimpleCommandPlugin): + """Adds support for moderators/admins/owner group chatter.""" + name = "mod_chatter" + depends = ['command_dispatcher', 'player_manager'] + commands = ["modchat", "mc"] + auto_activate = True + + def activate(self): + super(ModChatter, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + + @permissions(UserLevels.MODERATOR) + def modchat(self, data): + """Allows mod-only chatting.\nSyntax: /modchat (msg)""" + now = datetime.now() + if self.config.chattimestamps: + timestamp = "^red;<" + now.strftime("%H:%M") + "> ^yellow;" + else: + timestamp = "" + if len(data) == 0: + self.protocol.send_chat_message(self.modchat.__doc__) + return + try: + message = " ".join(data) + for protocol in self.factory.protocols.itervalues(): + if protocol.player.access_level >= UserLevels.MODERATOR: + protocol.send_chat_message(timestamp + + "%sModChat: ^yellow;<%s^yellow;> %s%s" % (self.config.colors["admin"], self.protocol.player.colored_name(self.config.colors), + self.config.colors["admin"],message.decode("utf-8"))) + self.logger.info("Received an admin message from %s. Message: %s", self.protocol.player.name, + message.decode("utf-8")) + except ValueError as e: + self.protocol.send_chat_message(self.modchat.__doc__) + except TypeError as e: + self.protocol.send_chat_message(self.modchat.__doc__) + + @permissions(UserLevels.MODERATOR) + def mc(self, data): + """Allows mod-only chatting.\nSyntax: /modchat (msg)""" + self.modchat(data) diff --git a/plugins/motd_plugin/motd_plugin.py b/plugins/motd_plugin/motd_plugin.py index b45344b..d40fb39 100644 --- a/plugins/motd_plugin/motd_plugin.py +++ b/plugins/motd_plugin/motd_plugin.py @@ -28,16 +28,12 @@ def send_motd(self): #self.protocol.send_chat_message("^yellow;%s" % self._motd) if not self.config.server_name: self.config.server_name = "MY" - self.protocol.send_chat_message("^#00FFFF;WELCOME TO ^#FF0000;%s^#00FFFF; STARBOUND SERVER\n^yellow;------------------------------------------------------------------\n^green;Type: ^yellow;/help ^green;for list of available commands\n^green;Type: ^yellow;/starteritems ^green;for some starter items ^cyan;(once)\n^green;Type: ^yellow;/fuel ^green;to get your daily fuel supply ^cyan;(once a day)\n^green;Legend: ^#F7434C;Owner^yellow;, ^#C443F7;Admin^yellow;, ^#4385F7;Moderator^yellow;, ^#A0F743;Registered user^yellow;, Guest\n^yellow;------------------------------------------------------------------\n^cyan;%s" % (self.config.server_name, self._motd)) + self.protocol.send_chat_message("%s" % (self._motd)) @permissions(UserLevels.GUEST) def motd(self, data): """Displays the message of the day.\nSyntax: /motd""" self.send_motd() -# if len(data) == 0: -# self.send_motd() -# else: -# self.motd_set(data) @permissions(UserLevels.MODERATOR) def motd_set(self, motd): @@ -49,6 +45,4 @@ def motd_set(self, motd): self.send_motd() except: self.logger.exception("Couldn't change message of the day.") - raise - - + raise \ No newline at end of file diff --git a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py index a97e6e3..2e7bbed 100644 --- a/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py +++ b/plugins/new_player_greeter_plugin/new_player_greeter_plugin.py @@ -1,3 +1,4 @@ +from twisted.internet import reactor, defer from base_plugin import BasePlugin from utility_functions import give_item_to_player @@ -12,7 +13,7 @@ class NewPlayerGreeter(BasePlugin): def activate(self): super(NewPlayerGreeter, self).activate() - def after_connect_response(self, data): + def after_world_start(self, data): if self.protocol.player is not None and self.protocol.player.logged_in: my_storage = self.protocol.player.storage if not 'given_starter_items' in my_storage or my_storage['given_starter_items'] == "False": @@ -27,4 +28,4 @@ def give_items(self): give_item_to_player(self.protocol, item[0], item[1]) def send_greetings(self): - self.protocol.send_chat_message(self.config.plugin_config["message"]) + self.protocol.send_chat_message(self.config.plugin_config["message"]) \ No newline at end of file diff --git a/plugins/planet_protect/planet_protect_plugin.py b/plugins/planet_protect/planet_protect_plugin.py index 10407c6..255f857 100644 --- a/plugins/planet_protect/planet_protect_plugin.py +++ b/plugins/planet_protect/planet_protect_plugin.py @@ -1,15 +1,14 @@ -import re from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import UserLevels, permissions -from packets import entity_create, EntityType, star_string +from packets import entity_create, EntityType, star_string, entity_interact_result, InteractionType from utility_functions import extract_name class PlanetProtectPlugin(SimpleCommandPlugin): """ -Allows planets to be either protector or unprotected. On protected planets, -only admins can build. Planets are unprotected by default. -""" + Allows planets to be either protector or unprotected. On protected planets, + only admins can build. Planets are unprotected by default. + """ name = "planet_protect" description = "Protects planets." commands = ["protect", "unprotect", "protect_list", "protect_all"] @@ -18,7 +17,6 @@ class PlanetProtectPlugin(SimpleCommandPlugin): def activate(self): super(PlanetProtectPlugin, self).activate() bad_packets = self.config.plugin_config.get("bad_packets", []) - for n in ["on_" + n.lower() for n in bad_packets]: setattr(self, n, (lambda x: self.planet_check())) self.protected_planets = self.config.plugin_config.get("protected_planets", []) @@ -26,7 +24,6 @@ def activate(self): self.blacklist = self.config.plugin_config.get("blacklist", []) self.player_manager = self.plugins["player_manager"].player_manager self.protect_everything = self.config.plugin_config.get("protect_everything", []) - self.regexes = self.plugins['player_manager'].regexes self.block_all = False def planet_check(self): @@ -43,17 +40,20 @@ def planet_check(self): else: return True - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) def protect(self, data): """Protects the current planet. Only administrators and allowed players can build on protected planets.\nSyntax: /protect [player]""" + planet = self.protocol.player.planet on_ship = self.protocol.player.on_ship if len(data) == 0: addplayer = self.protocol.player.org_name first_name_color = self.protocol.player.colored_name(self.config.colors) else: + self.logger.info("stream: %s" % data) addplayer = data[0] try: addplayer, rest = extract_name(data) + self.logger.info("name: %s" % str(addplayer)) addplayer = self.player_manager.get_by_name(addplayer).org_name first_name_color = self.player_manager.get_by_org_name(addplayer).colored_name(self.config.colors) except: @@ -106,8 +106,7 @@ def protect_all(self, data): self.factory.broadcast("Planetary protection is now ^red;ENABLED") self.save() - - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) def protect_list(self, data): """Displays players registered to the protected planet.\nSyntax: /protect_list""" planet = self.protocol.player.planet @@ -121,7 +120,7 @@ def protect_list(self, data): else: self.protocol.send_chat_message("Planet is not protected!") - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) def unprotect(self, data): """Removes the protection from the current planet, or removes a registered player.\nSyntax: /unprotect [player]""" planet = self.protocol.player.planet @@ -162,31 +161,42 @@ def save(self): def on_entity_create(self, data): """Projectile protection check""" - """ if self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.ADMIN: name = self.protocol.player.org_name if name in self.player_planets[self.protocol.player.planet]: return True else: - entities = entity_create.parse(data.data) + entities = entity_create().parse(data.data) for entity in entities.entity: + self.logger.vdebug("Entity Type: %s", entity.entity_type) if entity.entity_type == EntityType.PROJECTILE: - #if self.block_all: return False - p_type = star_string("").parse(entity.entity) + self.logger.vdebug("projectile detected") + if self.block_all: + return False + p_type = star_string("").parse(entity.payload) + self.logger.vdebug("projectile: %s", p_type) if p_type in self.blacklist: - self.logger.info( - "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", - self.protocol.player.org_name, p_type) + self.logger.vdebug("%s",self.blacklist) + if p_type in ['water']: + self.logger.vdebug( + "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", + self.protocol.player.org_name, p_type) + else: + self.logger.info( + "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", + self.protocol.player.org_name, p_type) return False - """ - if self.protect_everything and self.protocol.player.access_level < UserLevels.REGISTERED and not self.protocol.player.on_ship: - entities = entity_create.parse(data.data) - for entity in entities.entity: - if entity.entity_type == EntityType.PROJECTILE: - if self.block_all: return False - p_type = star_string("").parse(entity.entity) - if p_type in self.blacklist: - self.logger.info( - "Player %s attempted to use a prohibited projectile, %s, on a protected planet.", - self.protocol.player.org_name, p_type) - return False + + def on_entity_interact_result(self, data): + """Chest protection""" + if self.protocol.player.planet in self.protected_planets and self.protocol.player.access_level < UserLevels.ADMIN: + self.logger.vdebug("User %s attmepted to interact on a protected planet.", self.protocol.player.name) + name = self.protocol.player.org_name + if name in self.player_planets[self.protocol.player.planet]: + return True + else: + entity = entity_interact_result().parse(data.data) + if entity.interaction_type == InteractionType.OPEN_CONTAINER: + self.logger.vdebug("User %s attmepted to open container ID %s", self.protocol.player.name, entity.target_entity_id) + self.logger.vdebug("This is not permitted.") + return False \ No newline at end of file diff --git a/plugins/planet_visitor_announcer/planet_visitor_announcer.py b/plugins/planet_visitor_announcer/planet_visitor_announcer.py index 65d31a4..b55ca8e 100644 --- a/plugins/planet_visitor_announcer/planet_visitor_announcer.py +++ b/plugins/planet_visitor_announcer/planet_visitor_announcer.py @@ -1,10 +1,10 @@ from base_plugin import BasePlugin -from packets import warp_command +from packets import player_warp from twisted.internet import reactor class PlanetVisitorAnnouncer(BasePlugin): """ - Broadcasts a message whenever a player joins or leaves the server. + Broadcasts a message whenever a player beams down to a planet. """ name = "planet_visitor_announcer_plugin" auto_activate = True @@ -12,12 +12,10 @@ class PlanetVisitorAnnouncer(BasePlugin): def activate(self): super(PlanetVisitorAnnouncer, self).activate() - def after_warp_command(self, data): - w = warp_command().parse(data.data) - if w.warp_type == "WARP_DOWN" or w.warp_type == "WARP_HOME": + def after_player_warp(self, data): + w = player_warp().parse(data.data) + if w.warp_type == "WARP_TO_ORBITED_WORLD" or w.warp_type == "WARP_TO_HOME_WORLD": reactor.callLater(1, self.announce_on_planet, self.protocol.player) def announce_on_planet(self, who_beamed): - self.factory.broadcast_planet("%s^green; beamed down to the planet" % who_beamed.colored_name(self.config.colors), planet=self.protocol.player.planet) - - + self.factory.broadcast_planet("%s^green; beamed down to the planet" % who_beamed.colored_name(self.config.colors), planet=self.protocol.player.planet) \ No newline at end of file diff --git a/plugins/plugin_manager_plugin/plugin_manager_plugin.py b/plugins/plugin_manager_plugin/plugin_manager_plugin.py index 4eb2232..2cce4e8 100644 --- a/plugins/plugin_manager_plugin/plugin_manager_plugin.py +++ b/plugins/plugin_manager_plugin/plugin_manager_plugin.py @@ -13,7 +13,7 @@ class PluginManagerPlugin(SimpleCommandPlugin): def plugin_manager(self): return self.protocol.plugin_manager - @permissions(UserLevels.OWNER) + @permissions(UserLevels.ADMIN) def plugin_list(self, data): """Displays all currently loaded plugins.\nSyntax: /plugin_list""" self.protocol.send_chat_message("Currently loaded plugins: ^yellow;%s" % "^green;, ^yellow;".join( @@ -26,6 +26,7 @@ def plugin_list(self, data): @permissions(UserLevels.OWNER) def plugin_disable(self, data): """Disables a currently activated plugin.\nSyntax: /plugin_disable (plugin name)""" + self.logger.debug("disable_plugin called: %s" " ".join(data)) if len(data) == 0: self.protocol.send_chat_message("You have to specify a plugin.") return @@ -47,6 +48,7 @@ def plugin_disable(self, data): @permissions(UserLevels.OWNER) def plugin_enable(self, data): """Enables a currently deactivated plugin.\nSyntax: /plugin_enable (plugin name)""" + self.logger.debug("enable_plugin called: %s", " ".join(data)) if len(data) == 0: self.protocol.send_chat_message("You have to specify a plugin.") return @@ -69,6 +71,8 @@ def help(self, data): func = self.plugins['command_dispatcher'].commands.get(command, None) if func is None: self.protocol.send_chat_message("Couldn't find a command with the name ^yellow;%s" % command) + elif func.level > self.protocol.player.access_level: + self.protocol.send_chat_message("You do not have access to this command.") else: #self.protocol.send_chat_message("%s%s: %s" % (self.config.command_prefix, command, func.__doc__)) self.protocol.send_chat_message("%s" % func.__doc__) @@ -80,4 +84,4 @@ def help(self, data): available.sort(key=str.lower) self.protocol.send_chat_message( "Available commands: ^yellow;%s\n^green;Get more help on commands with ^yellow;/help [command]" % "^green;, ^yellow;".join(available)) - return True \ No newline at end of file + return True diff --git a/plugins/poi_plugin/__init__.py b/plugins/poi_plugin/__init__.py new file mode 100644 index 0000000..766075f --- /dev/null +++ b/plugins/poi_plugin/__init__.py @@ -0,0 +1 @@ +from poi_plugin import PointsofInterest diff --git a/plugins/poi_plugin/poi_plugin.py b/plugins/poi_plugin/poi_plugin.py new file mode 100644 index 0000000..37b81a2 --- /dev/null +++ b/plugins/poi_plugin/poi_plugin.py @@ -0,0 +1,124 @@ +import json +from base_plugin import SimpleCommandPlugin +from plugins.core.player_manager import permissions, UserLevels +from packets import Packets, fly_ship, fly_ship_write +from utility_functions import build_packet + + +class PointsofInterest(SimpleCommandPlugin): + """ + Plugin that allows admins to define Planets of Interest (PoI) any player can /poi to. + """ + name = "poi_plugin" + depends = ['command_dispatcher', 'player_manager'] + commands = ["poi_set", "poi_del", "poi", "spawn"] + auto_activate = True + + def activate(self): + super(PointsofInterest, self).activate() + self.player_manager = self.plugins['player_manager'].player_manager + try: + with open("./config/pois.json") as f: + self.pois = json.load(f) + except: + self.pois = [] + + @permissions(UserLevels.ADMIN) + def poi_set(self, name): + """Sets current planet as Planet of Interest (PoI).\nSyntax: /poi_set (name)""" + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + self.protocol.send_chat_message(self.poi_set.__doc__) + return + planet = self.protocol.player.planet + on_ship = self.protocol.player.on_ship + if on_ship: + self.protocol.send_chat_message("You need to be on a planet!") + return + for warp in self.pois: + if warp[0] == planet: + self.protocol.send_chat_message("The planet you're on is already set as a PoI: ^yellow;" + warp[1]) + return + if warp[1] == name: + self.protocol.send_chat_message("Planet of Interest named ^yellow;%s^green; already exists." % name) + return + self.pois.append([planet, name]) + self.protocol.send_chat_message("Planet of Interest ^yellow;%s^green; added." % name) + self.savepois() + + @permissions(UserLevels.ADMIN) + def poi_del(self, name): + """Removes current planet as Planet of Interest (PoI).\nSyntax: /poi_del (name)""" + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + self.protocol.send_chat_message(self.poi_del.__doc__) + return + for warp in self.pois: + if warp[1] == name: + self.pois.remove(warp) + self.protocol.send_chat_message("Planet of Interest ^yellow;%s^green; removed." % name) + self.savepois() + return + self.protocol.send_chat_message("There is no PoI named: ^yellow;%s^green;." % name) + + @permissions(UserLevels.GUEST) + def poi(self, name): + """Warps your ship to a Planet of Interest (PoI).\nSyntax: /poi [name] *omit name for a list of PoI's""" + name = " ".join(name).strip().strip("\t") + if len(name) == 0: + warps = [] + for warp in self.pois: + if warps != "": + warps.append(warp[1]) + warpnames = "^green;, ^yellow;".join(warps) + if warpnames == "": warpnames = "^gray;(none)^green;" + self.protocol.send_chat_message(self.poi.__doc__) + self.protocol.send_chat_message("List of PoI's: ^yellow;" + warpnames) + return + + on_ship = self.protocol.player.on_ship + if not on_ship: + self.protocol.send_chat_message("You need to be on a ship!") + return + + for warp in self.pois: + if warp[1] == name: + x, y, z, planet, satellite = warp[0].split(":") + x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) + warp_packet = build_packet(Packets.FLY_SHIP, + fly_ship_write(x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % name) + return + self.protocol.send_chat_message("There is no PoI named ^yellow;%s^green;." % name) + + @permissions(UserLevels.GUEST) + def spawn(self, data): + """Warps your ship to spawn.\nSyntax: /spawn""" + for warp in self.pois: + if warp[1] == 'spawn': + x, y, z, planet, satellite = warp[0].split(":") + x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) + warp_packet = build_packet(Packets.FLY_SHIP, + fly_ship_write(x=x, + y=y, + z=z, + planet=planet, + satellite=satellite)) + self.protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warp drive engaged! Warping to ^yellow;%s^green;." % 'Spawn') + return + else: + self.protocol.send_chat_message("The spawn planet must be set first!") + + def savepois(self): + try: + with open("./config/pois.json", "w") as f: + json.dump(self.pois, f) + except: + self.logger.exception("Couldn't save PoI's.") + raise diff --git a/plugins/starteritems/starteritems_plugin.py b/plugins/starteritems/starteritems_plugin.py index dcb0ef2..0b545ca 100644 --- a/plugins/starteritems/starteritems_plugin.py +++ b/plugins/starteritems/starteritems_plugin.py @@ -37,4 +37,4 @@ def give_items(self): give_item_to_player(self.protocol, item[0], item[1]) def send_greetings(self): - self.protocol.send_chat_message(self.config.plugin_config["message"]) + self.protocol.send_chat_message(self.config.plugin_config["message"]) \ No newline at end of file diff --git a/plugins/warpy_plugin/warpy_plugin.py b/plugins/warpy_plugin/warpy_plugin.py index 0e836a1..e3cd3d6 100644 --- a/plugins/warpy_plugin/warpy_plugin.py +++ b/plugins/warpy_plugin/warpy_plugin.py @@ -1,6 +1,7 @@ +# -*- coding: UTF-8 -*- from base_plugin import SimpleCommandPlugin from plugins.core.player_manager import permissions, UserLevels -from packets import warp_command_write, Packets, warp_command +from packets import Packets, player_warp, player_warp_write, fly_ship, fly_ship_write from utility_functions import build_packet, move_ship_to_coords, extract_name @@ -10,14 +11,14 @@ class Warpy(SimpleCommandPlugin): """ name = "warpy_plugin" depends = ['command_dispatcher', 'player_manager'] - commands = ["warp", "warp_ship"] + commands = ["warp", "warp_ship", "outpost"] auto_activate = True def activate(self): super(Warpy, self).activate() self.player_manager = self.plugins['player_manager'].player_manager - @permissions(UserLevels.ADMIN) + @permissions(UserLevels.MODERATOR) def warp(self, name): """Warps you to a player's ship (or player to player).\nSyntax: /warp [player] (to player)""" if len(name) == 0: @@ -59,6 +60,19 @@ def warp_ship(self, location): return self.move_player_ship_to_other(first_name, second_name) + @permissions(UserLevels.MODERATOR) + def outpost(self, name): + """Warps you (or another player) to the outpost.\nSyntax: /outpost [player]""" + if len(name) == 0: + self.warp_player_to_outpost(self.protocol.player.name) + else: + try: + player_name, rest = extract_name(name) + except ValueError as e: + self.protocol.send_chat_message(str(e)) + return + self.warp_player_to_outpost(player_name) + def warp_self_to_player(self, name): name = " ".join(name) self.warp_player_to_player(self.protocol.player.name, name) @@ -70,12 +84,14 @@ def warp_player_to_player(self, from_string, to_string): if to_player is not None: from_protocol = self.factory.protocols[from_player.protocol] if from_player is not to_player: - warp_packet = build_packet(Packets.WARP_COMMAND, - warp_command_write(t="WARP_OTHER_SHIP", - player=to_player.name.encode('utf-8'))) + self.logger.debug("target: %s", to_player.uuid) + warp_packet = build_packet(Packets.PLAYER_WARP, + player_warp_write(t="WARP_TO", + world_id=to_player.uuid)) else: - warp_packet = build_packet(Packets.WARP_COMMAND, - warp_command_write(t='WARP_UP')) + warp_packet = build_packet(Packets.PLAYER_WARP, + player_warp_write(t="WARP_TO_OWN_SHIP", + world_id=None)) from_protocol.client_protocol.transport.write(warp_packet) if from_string != to_string: self.protocol.send_chat_message("Warped ^yellow;%s^green; to ^yellow;%s^green;." % (from_string, to_string)) @@ -103,8 +119,7 @@ def move_player_ship(self, protocol, location): z = int(location.pop()) y = int(location.pop()) x = int(location.pop()) - sector = location.pop() - move_ship_to_coords(protocol, sector, x, y, z, planet, satellite) + move_ship_to_coords(protocol, x, y, z, planet, satellite) def move_own_ship_to_player(self, player_name): t = self.player_manager.get_logged_in_by_name(player_name) @@ -138,3 +153,16 @@ def move_player_ship_to_other(self, from_player, to_player): self.move_player_ship(self.factory.protocols[f.protocol], t.planet.split(":")) self.protocol.send_chat_message("Warp drive engaged. Warping ^yellow;%s^green; to ^yellow;%s^green;." % (from_player, to_player)) + def warp_player_to_outpost(self, player_string): + self.logger.debug("Warp player-to-outpost command called by %s: sending %s to the outpost", self.protocol.player.name, player_string) + player_to_send = self.player_manager.get_logged_in_by_name(player_string) + if player_to_send is not None: + player_protocol = self.factory.protocols[player_to_send.protocol] + warp_packet = build_packet(Packets.PLAYER_WARP, + player_warp_write(t="WARP_TO", + world_id="outpost")) + player_protocol.client_protocol.transport.write(warp_packet) + self.protocol.send_chat_message("Warped ^yellow;%s^green; to the outpost." % player_string) + else: + self.protocol.send_chat_message("No player by the name ^yellow;%s^green; found." % player_string) + self.protocol.send_chat_message(self.warp.__doc__) diff --git a/requirements.txt b/requirements.txt index 83d6826..38063ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,5 @@ sqlalchemy twisted enum34 construct -nose tornado -sqlite3 +nose \ No newline at end of file diff --git a/server.py b/server.py index 6070a4f..0c335c4 100644 --- a/server.py +++ b/server.py @@ -3,18 +3,18 @@ #import gettext import locale import logging +import logging.handlers from uuid import uuid4 import sys import socket import datetime -import construct from twisted.internet import reactor from twisted.internet.error import CannotListenError from twisted.internet.protocol import ClientFactory, ServerFactory, Protocol, connectionDone +from twisted.internet.task import LoopingCall from construct import Container import construct.core -from twisted.internet.task import LoopingCall from config import ConfigurationManager from packet_stream import PacketStream @@ -22,8 +22,15 @@ from plugin_manager import PluginManager, route, FatalPluginError from utility_functions import build_packet -VERSION = "1.4.3" +VERSION = "1.5" + +VDEBUG_LVL = 9 +logging.addLevelName(VDEBUG_LVL, "VDEBUG") +def vdebug(self, message, *args, **kws): + if self.isEnabledFor(VDEBUG_LVL): + self._log(VDEBUG_LVL, message, args, **kws) +logging.Logger.vdebug = vdebug def port_check(upstream_hostname, upstream_port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -49,64 +56,71 @@ def __init__(self): """ """ self.id = str(uuid4().hex) + logger.vdebug("Creating protocol with ID %s.", self.id) self.factory.protocols[self.id] = self self.player = None self.state = None + logger.debug("Trying to initialize configuration manager.") self.config = ConfigurationManager() self.parsing = False self.buffering_packet = None self.after_write_callback = None self.plugin_manager = None self.call_mapping = { - packets.Packets.PROTOCOL_VERSION: self.protocol_version, - packets.Packets.CONNECT_RESPONSE: self.connect_response, - packets.Packets.SERVER_DISCONNECT: self.server_disconnect, - packets.Packets.HANDSHAKE_CHALLENGE: self.handshake_challenge, - packets.Packets.CHAT_RECEIVED: self.chat_received, - packets.Packets.UNIVERSE_TIME_UPDATE: self.universe_time_update, - packets.Packets.CLIENT_CONNECT: self.client_connect, - packets.Packets.CLIENT_DISCONNECT: self.client_disconnect, - packets.Packets.HANDSHAKE_RESPONSE: self.handshake_response, - packets.Packets.WARP_COMMAND: self.warp_command, - packets.Packets.CHAT_SENT: self.chat_sent, - packets.Packets.CLIENT_CONTEXT_UPDATE: self.client_context_update, - packets.Packets.WORLD_START: self.world_start, - packets.Packets.WORLD_STOP: self.world_stop, - packets.Packets.TILE_ARRAY_UPDATE: self.tile_array_update, - packets.Packets.TILE_UPDATE: self.tile_update, - packets.Packets.CELESTIALRESPONSE: lambda x: True, - packets.Packets.CELESTIALREQUEST: lambda x: True, - packets.Packets.TILE_LIQUID_UPDATE: self.tile_liquid_update, - packets.Packets.TILE_DAMAGE_UPDATE: self.tile_damage_update, - packets.Packets.TILE_MODIFICATION_FAILURE: self.tile_modification_failure, - packets.Packets.GIVE_ITEM: self.item, - packets.Packets.SWAP_IN_CONTAINER_RESULT: self.swap_in_container_result, - packets.Packets.ENVIRONMENT_UPDATE: self.environment_update, - packets.Packets.ENTITY_INTERACT_RESULT: self.entity_interact_result, - packets.Packets.MODIFY_TILE_LIST: self.modify_tile_list, - packets.Packets.DAMAGE_TILE: self.damage_tile, - packets.Packets.DAMAGE_TILE_GROUP: self.damage_tile_group, - packets.Packets.REQUEST_DROP: self.request_drop, - packets.Packets.SPAWN_ENTITY: self.spawn_entity, - packets.Packets.ENTITY_INTERACT: self.entity_interact, - packets.Packets.CONNECT_WIRE: self.connect_wire, - packets.Packets.DISCONNECT_ALL_WIRES: self.disconnect_all_wires, - packets.Packets.OPEN_CONTAINER: self.open_container, - packets.Packets.CLOSE_CONTAINER: self.close_container, - packets.Packets.SWAP_IN_CONTAINER: self.swap_in_container, - packets.Packets.ITEM_APPLY_IN_CONTAINER: self.item_apply_in_container, - packets.Packets.START_CRAFTING_IN_CONTAINER: self.start_crafting_in_container, - packets.Packets.STOP_CRAFTING_IN_CONTAINER: self.stop_crafting_in_container, - packets.Packets.BURN_CONTAINER: self.burn_container, - packets.Packets.CLEAR_CONTAINER: self.clear_container, - packets.Packets.WORLD_UPDATE: self.world_update, - packets.Packets.ENTITY_CREATE: self.entity_create, - packets.Packets.ENTITY_UPDATE: self.entity_update, - packets.Packets.ENTITY_DESTROY: self.entity_destroy, - packets.Packets.DAMAGE_NOTIFICATION: self.damage_notification, - packets.Packets.STATUS_EFFECT_REQUEST: self.status_effect_request, - packets.Packets.UPDATE_WORLD_PROPERTIES: self.update_world_properties, - packets.Packets.HEARTBEAT: self.heartbeat, + packets.Packets.PROTOCOL_VERSION: self.protocol_version, # 0 + packets.Packets.SERVER_DISCONNECT: self.server_disconnect, # 1 + packets.Packets.CONNECT_RESPONSE: self.connect_response, # 2 + packets.Packets.HANDSHAKE_CHALLENGE: self.handshake_challenge, #3 + packets.Packets.CHAT_RECEIVED: self.chat_received, # 4 + packets.Packets.UNIVERSE_TIME_UPDATE: self.universe_time_update, # 5 + packets.Packets.CELESTIAL_RESPONSE: lambda x: True, # 6 + packets.Packets.CLIENT_CONNECT: self.client_connect, # 7 + packets.Packets.CLIENT_DISCONNECT_REQUEST: self.client_disconnect_request, # 8 + packets.Packets.HANDSHAKE_RESPONSE: self.handshake_response, # 9 + packets.Packets.PLAYER_WARP: self.player_warp, # 10 + packets.Packets.FLY_SHIP: self.fly_ship, # 11 + packets.Packets.CHAT_SENT: self.chat_sent, # 12 + packets.Packets.CELESTIAL_REQUEST: self.celestial_request, # 13 + packets.Packets.CLIENT_CONTEXT_UPDATE: self.client_context_update, # 14 + packets.Packets.WORLD_START: self.world_start, # 15 + packets.Packets.WORLD_STOP: self.world_stop, # 16 + packets.Packets.CENTRAL_STRUCTURE_UPDATE: self.central_structure_update, # 17 + packets.Packets.TILE_ARRAY_UPDATE: self.tile_array_update, # 18 + packets.Packets.TILE_UPDATE: self.tile_update, # 19 + packets.Packets.TILE_LIQUID_UPDATE: self.tile_liquid_update, # 20 + packets.Packets.TILE_DAMAGE_UPDATE: self.tile_damage_update, # 21 + packets.Packets.TILE_MODIFICATION_FAILURE: self.tile_modification_failure, #22 + packets.Packets.GIVE_ITEM: self.give_item, # 23 + packets.Packets.SWAP_IN_CONTAINER_RESULT: self.swap_in_container_result, # 24 + packets.Packets.ENVIRONMENT_UPDATE: self.environment_update, # 25 + packets.Packets.ENTITY_INTERACT_RESULT: self.entity_interact_result, # 26 + packets.Packets.UPDATE_TILE_PROTECTION: lambda x: True, # 27 + packets.Packets.MODIFY_TILE_LIST: self.modify_tile_list, # 28 + packets.Packets.DAMAGE_TILE_GROUP: self.damage_tile_group, # 29 + packets.Packets.COLLECT_LIQUID: lambda x: True, # 30 + packets.Packets.REQUEST_DROP: self.request_drop, # 31 + packets.Packets.SPAWN_ENTITY: self.spawn_entity, # 32 + packets.Packets.ENTITY_INTERACT: self.entity_interact, # 33 + packets.Packets.CONNECT_WIRE: self.connect_wire, # 34 + packets.Packets.DISCONNECT_ALL_WIRES: self.disconnect_all_wires, # 35 + packets.Packets.OPEN_CONTAINER: self.open_container, # 36 + packets.Packets.CLOSE_CONTAINER: self.close_container, # 37 + packets.Packets.SWAP_IN_CONTAINER: self.swap_in_container, # 38 + packets.Packets.ITEM_APPLY_IN_CONTAINER: self.item_apply_in_container, # 39 + packets.Packets.START_CRAFTING_IN_CONTAINER: self.start_crafting_in_container, # 40 + packets.Packets.STOP_CRAFTING_IN_CONTAINER: self.stop_crafting_in_container, # 41 + packets.Packets.BURN_CONTAINER: self.burn_container, # 42 + packets.Packets.CLEAR_CONTAINER: self.clear_container, # 43 + packets.Packets.WORLD_CLIENT_STATE_UPDATE: self.world_client_state_update, # 44 + packets.Packets.ENTITY_CREATE: self.entity_create, # 45 + packets.Packets.ENTITY_UPDATE: self.entity_update, # 46 + packets.Packets.ENTITY_DESTROY: self.entity_destroy, # 47 + packets.Packets.HIT_REQUEST: self.hit_request, # 48 + packets.Packets.DAMAGE_REQUEST: lambda x: True, # 49 + packets.Packets.DAMAGE_NOTIFICATION: self.damage_notification, # 50 + packets.Packets.CALL_SCRIPTED_ENTITY: lambda x: True, # 51 + packets.Packets.UPDATE_WORLD_PROPERTIES: self.update_world_properties, # 52 + packets.Packets.HEARTBEAT: self.heartbeat, # 53 } self.client_protocol = None self.packet_stream = PacketStream(self) @@ -136,7 +150,10 @@ def string_received(self, packet): Processing of parsed data is handled in handle_starbound_packets() :rtype : None """ - if 48 >= packet.id: + if 53 >= packet.id: + # DEBUG - print all packet IDs going to client + # if packet.id not in [14, 44, 45, 46, 47, 51, 53]: + # logger.info("From Client: %s", packet.id) if self.handle_starbound_packets(packet): self.client_protocol.transport.write( packet.original_data) @@ -205,6 +222,10 @@ def world_start(self, data): def world_stop(self, data): return True + @route + def central_structure_update(self, data): + return True + @route def tile_array_update(self, data): return True @@ -306,7 +327,7 @@ def clear_container(self, data): return True @route - def world_update(self, data): + def world_client_state_update(self, data): return True @route @@ -321,6 +342,10 @@ def entity_update(self, data): def entity_destroy(self, data): return True + @route + def hit_request(self, data): + return True + @route def status_effect_request(self, data): return True @@ -355,6 +380,16 @@ def chat_sent(self, data): """ return True + @route + def celestial_request(self, data): + """ + Called when the client requests celestial data...? + + :param data: Parsed chat packet. + :rtype : bool + """ + return True + @route def damage_notification(self, data): return True @@ -370,7 +405,7 @@ def client_connect(self, data): return True @route - def client_disconnect(self, player): + def client_disconnect_request(self, player): """ Called when the client signals that it is about to disconnect from the Starbound server. @@ -380,11 +415,21 @@ def client_disconnect(self, player): return True @route - def warp_command(self, data): + def player_warp(self, data): """ Called when the players issues a warp. - :param data: The warp_command data. + :param data: The player_warp data. + :rtype : bool + """ + return True + + @route + def fly_ship(self, data): + """ + Called when the players moves their ship. + + :param data: The fly_ship data. :rtype : bool """ return True @@ -392,11 +437,11 @@ def warp_command(self, data): def handle_starbound_packets(self, p): """ This function is the meat of it all. Every time a full packet with - a derived ID <= 48, it is passed through here. + a derived ID <= 53, it is passed through here. """ return self.call_mapping[p.id](p) - def send_chat_message(self, text, channel=0, world='', name=''): + def send_chat_message(self, text, mode='BROADCAST', channel='', name=''): """ Convenience function to send chat messages to the client. Note that this does *not* send messages to the server at large; broadcast should be @@ -404,8 +449,7 @@ def send_chat_message(self, text, channel=0, world='', name=''): otherwise. :param text: Message text, may contain multiple lines. - :param channel: The chat channel/context. 0 is global, 1 is planet. - :param world: World + :param channel: The chat channel/context. :param name: The name to display before the message. Blank leaves no brackets, otherwise it will be displayed as ``. :return: None @@ -415,14 +459,21 @@ def send_chat_message(self, text, channel=0, world='', name=''): for line in lines: self.send_chat_message(line) return - chat_data = packets.chat_received().build(Container(chat_channel=channel, - world=world, + if self.player is not None: + logger.vdebug(('Calling send_chat_message from player %s on channel' + ' %s with mode %s with reported username of %s with' + ' message: %s'), self.player.name, channel, mode, name, text) + chat_data = packets.chat_received().build(Container(mode=mode, + channel=channel, client_id=0, name=name, message=text.encode("utf-8"))) + logger.vdebug("Built chat payload. Data: %s", chat_data.encode("hex")) chat_packet = build_packet(packets.Packets.CHAT_RECEIVED, chat_data) + logger.vdebug("Built chat packet. Data: %s", chat_packet.encode("hex")) self.transport.write(chat_packet) + logger.vdebug("Sent chat message with text: %s", text) def write(self, data): """ @@ -440,10 +491,10 @@ def connectionLost(self, reason=connectionDone): """ try: if self.client_protocol is not None: - x = build_packet(packets.Packets.CLIENT_DISCONNECT, - packets.client_disconnect().build(Container(data=0))) + x = build_packet(packets.Packets.CLIENT_DISCONNECT_REQUEST, + packets.client_disconnect_request().build(Container(data=0))) if self.player is not None and self.player.logged_in: - self.client_disconnect(x) + self.client_disconnect_request(x) self.client_protocol.transport.write(x) self.client_protocol.transport.abortConnection() except: @@ -453,6 +504,7 @@ def connectionLost(self, reason=connectionDone): self.factory.protocols.pop(self.id) except: logger.info("Protocol was not in factory list. This should not happen.") + logger.info("protocol id: %s" % self.id) finally: logger.info("Lost connection from IP: %s", self.transport.getPeer().host) self.transport.abortConnection() @@ -479,7 +531,6 @@ def connectionMade(self): :return: None """ self.server_protocol.client_protocol = self - self.parsing = False def string_received(self, packet): """ @@ -494,6 +545,9 @@ def string_received(self, packet): :return: None """ try: + # DEBUG - print all packet IDs coming from client + # if packet.id not in [5, 14, 25, 45, 46, 47, 51, 53]: + # logger.info("From Server: %s", packet.id) if self.server_protocol.handle_starbound_packets( packet): self.server_protocol.write(packet.original_data) @@ -518,6 +572,11 @@ def dataReceived(self, data): else: self.packet_stream += data + def disconnect(self): + x = build_packet(packets.Packets.CLIENT_DISCONNECT_REQUEST, packets.client_disconnect_request().build(Container(data=0))) + self.transport.write(x) + self.transport.abortConnection() + class StarryPyServerFactory(ServerFactory): """ @@ -549,14 +608,13 @@ def stopFactory(self): self.config.save() self.plugin_manager.die() - def broadcast(self, text, channel=1, world='', name=''): + def broadcast(self, text, mode=1, name=''): """ Convenience method to send a broadcasted message to all clients on the server. :param text: Message text - :param channel: Channel to broadcast on. 0 is global, 1 is planet. - :param world: World + :param mode: Channel to broadcast on. 0 is global, 1 is planet. :param name: The name to prepend before the message, format is :return: None """ @@ -589,26 +647,30 @@ def buildProtocol(self, address): :rtype : Protocol """ + logger.vdebug("Building protocol to address %s", address) p = ServerFactory.buildProtocol(self, address) return p def reap_dead_protocols(self): + logger.vdebug("Reaping dead connections.") count = 0 start_time = datetime.datetime.now() for protocol in self.protocols.itervalues(): - if ( - protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: + if (protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: + logger.debug("Reaping protocol %s. Reason: Server protocol timeout.", protocol.id) protocol.connectionLost() count += 1 continue - if protocol.client_protocol is not None and ( - protocol.client_protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: + if protocol.client_protocol is not None and (protocol.client_protocol.packet_stream.last_received_timestamp - start_time).total_seconds() > self.config.reap_time: protocol.connectionLost() + logger.debug("Reaping protocol %s. Reason: Client protocol timeout.", protocol.id) count += 1 if count == 1: logger.info("1 connection reaped.") elif count > 1: logger.info("%d connections reaped.") + else: + logger.vdebug("No connections reaped.") class StarboundClientFactory(ClientFactory): @@ -618,48 +680,62 @@ class StarboundClientFactory(ClientFactory): protocol = ClientProtocol def __init__(self, server_protocol): + logger.vdebug("Client protocol instantiated.") self.server_protocol = server_protocol def buildProtocol(self, address): + logger.vdebug("Building protocol in StarboundClientFactory to address %s", address) protocol = ClientFactory.buildProtocol(self, address) protocol.server_protocol = self.server_protocol return protocol - - def init_localization(): try: locale.setlocale(locale.LC_ALL, '') except: locale.setlocale(locale.LC_ALL, 'en_US.utf8') - """try: - loc = locale.getlocale() - filename = "res/messages_%s.mo" % locale.getlocale()[0][0:2] - print "Opening message file %s for locale %s." % (filename, loc[0]) - trans = gettext.GNUTranslations(open(filename, "rb")) - except (IOError, TypeError, IndexError): - print "Locale not found. Using default messages." - trans = gettext.NullTranslations() - trans.install()""" + #try: + # loc = locale.getlocale() + # filename = "res/messages_%s.mo" % locale.getlocale()[0][0:2] + # print "Opening message file %s for locale %s." % (filename, loc[0]) + # trans = gettext.GNUTranslations(open(filename, "rb")) + #except (IOError, TypeError, IndexError): + # print "Locale not found. Using default messages." + # trans = gettext.NullTranslations() + #trans.install() if __name__ == '__main__': init_localization() + + print('Attempting initialization of configuration manager singleton.') + config = ConfigurationManager() + logger = logging.getLogger('starrypy') logger.setLevel(9) + log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s # %(message)s') + if config.log_level == 'DEBUG': + log_level = logging.DEBUG + elif config.log_level == 'VDEBUG': + log_level = "VDEBUG" + else: + log_level = logging.INFO - fh_w = logging.FileHandler("server.log") - fh_w.setLevel(logging.INFO) - sh = logging.StreamHandler(sys.stdout) - sh.setLevel(logging.INFO) - logger.addHandler(sh) - logger.addHandler(fh_w) - config = ConfigurationManager() - console_formatter = logging.Formatter(config.logging_format_console) - logfile_formatter = logging.Formatter(config.logging_format_logfile) - fh_w.setFormatter(logfile_formatter) - sh.setFormatter(console_formatter) + print('Setup console logging...') + console_handle = logging.StreamHandler(sys.stdout) + console_handle.setLevel(log_level) + logger.addHandler(console_handle) + console_handle.setFormatter(log_format) + + print('Setup file-based logging...') + logfile_handle = logging.handlers.TimedRotatingFileHandler("server.log", when='midnight', interval=5, backupCount=4) + logfile_handle.setLevel(log_level) + logger.addHandler(logfile_handle) + logfile_handle.setFormatter(log_format) if config.port_check: + logger.debug("Port check enabled. Performing port check to %s:%d", config.upstream_hostname, + config.upstream_port) + if not port_check(config.upstream_hostname, config.upstream_port): logger.critical("The starbound server is not connectable at the address %s:%d." % ( config.upstream_hostname, config.upstream_port)) @@ -667,8 +743,11 @@ def init_localization(): "Please ensure that you are running starbound_server on the correct port and that is reflected in the StarryPy configuration.") sys.exit() + logger.debug("Port check succeeded. Continuing.") + logger.info("Started StarryPy server version %s" % VERSION) factory = StarryPyServerFactory() + logger.debug("Attempting to listen on TCP port %d", factory.config.bind_port) try: reactor.listenTCP(factory.config.bind_port, factory, interface=factory.config.bind_address) diff --git a/utility_functions.py b/utility_functions.py index 2066520..68f7c74 100644 --- a/utility_functions.py +++ b/utility_functions.py @@ -37,7 +37,7 @@ def recursive_dictionary_update(d, u): def build_packet(packet_type, data): """ Convenience method to build packets for sending. - :param packet_type: An integer 1 <= packet_type <= 48 + :param packet_type: An integer 1 <= packet_type <= 53 :param data: Data to send. :return: The build packet. :rtype : str @@ -48,8 +48,7 @@ def build_packet(packet_type, data): class Planet(object): - def __init__(self, sector, x, y, z, planet, satellite): - self.sector = sector + def __init__(self, x, y, z, planet, satellite): self.x = x self.y = y self.z = z @@ -57,17 +56,16 @@ def __init__(self, sector, x, y, z, planet, satellite): self.satellite = satellite def __str__(self): - return "%s:%d:%d:%d:%d:%d" % (self.sector, self.x, self.y, self.z, self.planet, self.satellite) + return "%d:%d:%d:%d:%d" % (self.x, self.y, self.z, self.planet, self.satellite) -def move_ship_to_coords(protocol, sector, x, y, z, planet, satellite): +def move_ship_to_coords(protocol, x, y, z, planet, satellite): logger.info("Moving %s's ship to coordinates: %s", protocol.player.name, - ":".join((sector, str(x), str(y), str(z), str(planet), str(satellite)))) + ":".join((str(x), str(y), str(z), str(planet), str(satellite)))) x, y, z, planet, satellite = map(int, (x, y, z, planet, satellite)) - warp_packet = build_packet(packets.Packets.WARP_COMMAND, - packets.warp_command_write(t="MOVE_SHIP", sector=sector, x=x, y=y, z=z, - planet=planet, - satellite=satellite, player="".encode('utf-8'))) + warp_packet = build_packet(packets.Packets.FLY_SHIP, + packets.fly_ship_write(x=x, y=y, z=z, planet=planet, + satellite=satellite)) protocol.client_protocol.transport.write(warp_packet)