Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: LAN mode with fixed hour (option), user defined nicknames #147

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 110 additions & 23 deletions server.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/usr/bin/env python
#
import sys, time, socket, re
from math import floor
from world import World
import Queue
import SocketServer
import datetime
import random
import re
import requests
import sqlite3
import sys
import threading
import time
import traceback

DEFAULT_HOST = '0.0.0.0'
Expand Down Expand Up @@ -164,9 +164,21 @@ def send(self, *args):
self.send_raw(packet(*args))

class Model(object):
def __init__(self, seed):
def __init__(self, seed, lan_mode=False, force_hour=None, db_path=DB_PATH):
self.world = World(seed)
self.clients = []

self.lan_mode = lan_mode
self.db_path = db_path

if force_hour is not None:
# make the day progress VERY slowly, and start at indicated time
dl = 60000
force_hour %= 24
self.time_config = lambda: (force_hour*dl/24., dl)
else:
self.time_config = lambda: (time.time(), DAY_LENGTH)

self.queue = Queue.Queue()
self.commands = {
AUTHENTICATE: self.on_authenticate,
Expand All @@ -185,6 +197,12 @@ def __init__(self, seed):
(re.compile(r'^/help(?:\s+(\S+))?$'), self.on_help),
(re.compile(r'^/list$'), self.on_list),
]

if self.lan_mode:
self.patterns.extend([
(re.compile(r'^/nick(?:\s+(\S+))?$'), self.on_set_nick),
])

def start(self):
thread = threading.Thread(target=self.run)
thread.setDaemon(True)
Expand Down Expand Up @@ -285,7 +303,7 @@ def on_connect(self, client):
client.position = SPAWN_POINT
self.clients.append(client)
client.send(YOU, client.client_id, *client.position)
client.send(TIME, time.time(), DAY_LENGTH)
client.send(TIME, *self.time_config())
client.send(TALK, 'Welcome to Craft!')
client.send(TALK, 'Type "/help" for a list of commands.')
self.send_position(client)
Expand Down Expand Up @@ -315,16 +333,32 @@ def on_version(self, client, version):
# TODO: client.start() here
def on_authenticate(self, client, username, access_token):
user_id = None
if username and access_token:
url = 'https://craft.michaelfogleman.com/api/1/access'
payload = {
'username': username,
'access_token': access_token,
}
response = requests.post(url, data=payload)
if response.status_code == 200 and response.text.isdigit():
user_id = int(response.text)

if self.lan_mode:
# Use the IP and port as a starting point.
ip, port = client.request.getpeername()
if not username:
try:
username = socket.gethostbyaddr(ip)[0] + ':%s' % port
except:
username = '%s:%s' % (ip, port)
client.nick = username
client.send(TALK, 'Welcome %s' % client.nick)
user_id = ((int(ip.replace('.',''))*65536) + port) % 2**30
client.user_id = user_id
else:
if username and access_token:
url = 'https://craft.michaelfogleman.com/api/1/access'
payload = {
'username': username,
'access_token': access_token,
}
response = requests.post(url, data=payload)
if response.status_code == 200 and response.text.isdigit():
user_id = int(response.text)

client.user_id = user_id

if user_id is None:
client.nick = 'guest%d' % client.client_id
client.send(TALK, 'Visit craft.michaelfogleman.com to register!')
Expand Down Expand Up @@ -506,6 +540,28 @@ def on_spawn(self, client):
client.position = SPAWN_POINT
client.send(YOU, client.client_id, *client.position)
self.send_position(client)

def on_set_nick(self, client, new_nick=None):
if new_nick is None:
return self.on_help(client, 'nick')

NICK_RE = r'[a-z0-9A-Z_]{3,32}'
if not re.match(NICK_RE, new_nick):
client.send(TALK, 'Nicknames are 3 to 32 chars: A-Z a-Z or _')
return

if new_nick.lower() in [i.nick.lower() for i in self.clients
if client != i]:
client.send(TALK, 'That nickname is taken already.')
return

old_nick = client.nick
client.nick = new_nick
self.send_nick(client)
self.send_nicks(client)
for other in self.clients:
other.send(TALK, '%s is now nick-named: %s' % (old_nick, new_nick))

def on_goto(self, client, nick=None):
if nick is None:
clients = [x for x in self.clients if x != client]
Expand All @@ -529,6 +585,8 @@ def on_help(self, client, topic=None):
client.send(TALK, 'Type "t" to chat. Type "/" to type commands:')
client.send(TALK, '/goto [NAME], /help [TOPIC], /list, /login NAME, /logout')
client.send(TALK, '/offline [FILE], /online HOST [PORT], /pq P Q, /spawn, /view N')
if self.lan_mode:
client.send(TALK, '/nick NEW_NICKNAME')
return
topic = topic.lower().strip()
if topic == 'goto':
Expand Down Expand Up @@ -562,6 +620,10 @@ def on_help(self, client, topic=None):
elif topic == 'view':
client.send(TALK, 'Help: /view N')
client.send(TALK, 'Set viewing distance, 1 - 24.')
elif self.lan_mode:
if topic == 'nick':
client.send(TALK, 'Help: /nick NEW_NICKNAME')
client.send(TALK, 'Set your nickname.')
def on_list(self, client):
client.send(TALK,
'Players: %s' % ', '.join(x.nick for x in self.clients))
Expand Down Expand Up @@ -589,6 +651,8 @@ def send_disconnect(self, client):
continue
other.send(DISCONNECT, client.client_id)
def send_block(self, client, p, q, x, y, z, w):
log("Send block: %s" %
','.join([str(i) for i in (p, q, x, y, z, w)]))
for other in self.clients:
if other == client:
continue
Expand Down Expand Up @@ -640,20 +704,43 @@ def cleanup():
print >> sys.stderr, '%d of %d blocks will be cleaned up' % (count, total)

def main():
if len(sys.argv) == 2 and sys.argv[1] == 'cleanup':
import argparse

parser = argparse.ArgumentParser(description='Craft multi-user server')

parser.add_argument('--cleanup', action='store_true',
help="Wipe all existing world data")

parser.add_argument('--seed', default=None, type=int)
parser.add_argument('--host', default=DEFAULT_HOST)
parser.add_argument('--db', default=DB_PATH)
parser.add_argument('--port', '-p', default=DEFAULT_PORT, type=int,
help="Port to bind to. Default: %s" % DEFAULT_PORT)
parser.add_argument('--hour', default=None, type=int,
help="Make it the same time of day all the time (hour: 0...23).")
parser.add_argument('--lan', action='store_true',
help="No passwords, just hostname/ip. Lan use only!")

args = parser.parse_args()

host, port = args.host, args.port

if args.cleanup:
cleanup()
return
host, port = DEFAULT_HOST, DEFAULT_PORT
if len(sys.argv) > 1:
host = sys.argv[1]
if len(sys.argv) > 2:
port = int(sys.argv[2])

log('SERV', host, port)
model = Model(None)

model = Model(args.seed, lan_mode=args.lan, force_hour=args.hour, db_path=args.db)
model.start()

server = Server((host, port), Handler)
server.model = model
server.serve_forever()

try:
server.serve_forever()
except KeyboardInterrupt:
log("Stopped")

if __name__ == '__main__':
main()
sys.exit(main() or 0)