initial commit backend
This commit is contained in:
131
.gitignore
vendored
Normal file
131
.gitignore
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/python
|
||||||
|
# Edit at https://www.gitignore.io/?templates=python
|
||||||
|
|
||||||
|
### Python ###
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don’t work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/python
|
||||||
|
|
||||||
34
actors.py
Normal file
34
actors.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import chat.response as response
|
||||||
|
|
||||||
|
|
||||||
|
class ActorManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.actors = dict()
|
||||||
|
|
||||||
|
def add_character(self, char_dict):
|
||||||
|
name = char_dict['name']
|
||||||
|
self.actors[name] = char_dict
|
||||||
|
print(self.actors)
|
||||||
|
return response.build_new_character(None, char_dict)
|
||||||
|
|
||||||
|
def remove_actor(self, name):
|
||||||
|
if not name in self.actors:
|
||||||
|
return None # TODO FEHLERMELDUNG, CHARAKTER EXISTIERT NICHT
|
||||||
|
self.actors.pop(name)
|
||||||
|
return response.build_remove_actor(None, None, name)
|
||||||
|
|
||||||
|
def move_by(self, name, delta):
|
||||||
|
if not name in self.actors:
|
||||||
|
return None # TODO FEHLERMELDUNG, CHARAKTER EXISTIERT NICHT
|
||||||
|
actor = self.actors[name]
|
||||||
|
actor.tick += delta
|
||||||
|
return response.build_new_character(None, None, actor)
|
||||||
|
|
||||||
|
def move_to(self, name, tick):
|
||||||
|
if not name in self.actors:
|
||||||
|
return None # TODO FEHLERMELDUNG, CHARAKTER EXISTIERT NICHT
|
||||||
|
actor = self.actors[name]
|
||||||
|
actor.state = TickState.ACTING.value
|
||||||
|
actor.tick = tick
|
||||||
|
return response.build_new_character(None, None, actor)
|
||||||
|
|
||||||
0
chat/__init__.py
Normal file
0
chat/__init__.py
Normal file
79
chat/chat_commands.py
Normal file
79
chat/chat_commands.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import chat.dice as dice
|
||||||
|
import chat.response as response
|
||||||
|
from chat.command_exception import CommandException
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class CommandHandler:
|
||||||
|
def __init__(self):
|
||||||
|
self.commands = {
|
||||||
|
'/roll': self.custom_roll,
|
||||||
|
'/r': self.custom_roll,
|
||||||
|
'/whisper': self.whisper,
|
||||||
|
'/w': self.whisper
|
||||||
|
}
|
||||||
|
self.skill_map = {'Tera': {'Akrobatik': 25,
|
||||||
|
'Arkane Kunde': 10}
|
||||||
|
}
|
||||||
|
|
||||||
|
def handle(self, char, content):
|
||||||
|
message = content.strip('\n')
|
||||||
|
if message[0] == '.':
|
||||||
|
return self.skill_roll(char, message)
|
||||||
|
else:
|
||||||
|
message = message.split(' ', 1)
|
||||||
|
if (len(message) > 1) and (message[0] in self.commands):
|
||||||
|
func = self.commands[message[0]]
|
||||||
|
r = func(char, message)
|
||||||
|
return r
|
||||||
|
return response.build_public_message(char, content)
|
||||||
|
|
||||||
|
def custom_roll(self, char, message):
|
||||||
|
pattern = '(?P<dice_num>\d+)(?:d|w)(?P<dice_val>\d+)(?P<maths>(?:(?:\+|\-)\d+)*)'
|
||||||
|
command = re.sub('\s+', '', message[1])
|
||||||
|
|
||||||
|
match = re.match(pattern, command)
|
||||||
|
if match is None:
|
||||||
|
return response.build_system_message(char,
|
||||||
|
'Ungültige Formatierung des Befehls: {}'.format(' '.join(message)))
|
||||||
|
|
||||||
|
eyes, result = dice.custom_roll(int(match.group('dice_num')),
|
||||||
|
int(match.group('dice_val')),
|
||||||
|
match.group('maths'))
|
||||||
|
return response.build_dice_roll(char, ' '.join(message), eyes, result)
|
||||||
|
|
||||||
|
def skill_roll(self, char, message):
|
||||||
|
print(char)
|
||||||
|
print(message)
|
||||||
|
print(self.skill_map)
|
||||||
|
if char not in self.skill_map:
|
||||||
|
return response.build_system_message(char, 'Ungültiger Charakter: {}'.format(char))
|
||||||
|
|
||||||
|
pattern = '(?P<skill>[a-zA-ZäöüÄÖÜß .&]+)(?P<maths>(?:\s*(?:\+|\-)\s*\d+)*)(?P<type>[nrs]?)'
|
||||||
|
match = re.match(pattern, message)
|
||||||
|
if match is None:
|
||||||
|
return response.build_system_message(char,
|
||||||
|
'Unültige Formatierung des Befehls: {}'.format(message))
|
||||||
|
|
||||||
|
skills = self.skill_map[char]
|
||||||
|
if match.group('skill')[1:] not in skills:
|
||||||
|
return response.build_system_message(char,
|
||||||
|
'{} hat die Fertigkeit {} nicht.'.format(char, match.group('skill')))
|
||||||
|
|
||||||
|
skill = skills[match.group('skill')[1:]]
|
||||||
|
type_ = match.group('type')
|
||||||
|
if len(type_) == 0 or type_ == 'n':
|
||||||
|
type_ = dice.RollTypes.NORMAL
|
||||||
|
elif type_ == 'r':
|
||||||
|
type_ = dice.RollTypes.RISKY
|
||||||
|
else:
|
||||||
|
type_ = dice.RollTypes.SAFE
|
||||||
|
eyes, result = dice.skill_roll(skill,
|
||||||
|
match.group('maths'),
|
||||||
|
type_)
|
||||||
|
print(eyes, result)
|
||||||
|
return response.build_dice_roll(char, message, eyes, result)
|
||||||
|
|
||||||
|
def whisper(self, char, message):
|
||||||
|
recipient, message = message.split(' ', 1)
|
||||||
|
return response.build_private_message(char, message, recipient)
|
||||||
3
chat/command_exception.py
Normal file
3
chat/command_exception.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class CommandException(Exception):
|
||||||
|
def __init__(self, *args):
|
||||||
|
super().__init__(*args)
|
||||||
47
chat/dice.py
Normal file
47
chat/dice.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from enum import auto, Enum
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
class RollTypes(Enum):
|
||||||
|
NORMAL = auto()
|
||||||
|
RISKY = auto()
|
||||||
|
SAFE = auto()
|
||||||
|
|
||||||
|
|
||||||
|
def skill_roll(skill, modifiers, type_=RollTypes.NORMAL):
|
||||||
|
eyes, result = roll_dice(type_)
|
||||||
|
result += skill
|
||||||
|
if len(modifiers) > 0:
|
||||||
|
result += eval(modifiers)
|
||||||
|
|
||||||
|
return eyes, result
|
||||||
|
|
||||||
|
|
||||||
|
def custom_roll(n, eyes, modifiers=None):
|
||||||
|
eyes = n_random_eyes(n, eyes)
|
||||||
|
result = sum(eyes)
|
||||||
|
|
||||||
|
if len(modifiers) > 0:
|
||||||
|
result += eval(modifiers)
|
||||||
|
|
||||||
|
return eyes, result
|
||||||
|
|
||||||
|
|
||||||
|
def roll_dice(type_):
|
||||||
|
if type_ is RollTypes.NORMAL:
|
||||||
|
eyes = n_random_eyes(2)
|
||||||
|
result = sum(eyes)
|
||||||
|
elif type_ is RollTypes.RISKY:
|
||||||
|
eyes = n_random_eyes(4)
|
||||||
|
eyes.sort(reverse=True)
|
||||||
|
result = sum(eyes[:2])
|
||||||
|
else:
|
||||||
|
eyes = n_random_eyes(2)
|
||||||
|
result = max(eyes)
|
||||||
|
return eyes, result
|
||||||
|
|
||||||
|
|
||||||
|
def n_random_eyes(n, eyes=10):
|
||||||
|
return [random.randint(1, eyes) for _ in range(n)]
|
||||||
|
|
||||||
|
|
||||||
67
chat/response.py
Normal file
67
chat/response.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
from abc import ABC
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from events import Events
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Response(ABC):
|
||||||
|
sender: str
|
||||||
|
event: Events
|
||||||
|
|
||||||
|
def to_json(self) -> dict:
|
||||||
|
d = self.__dict__
|
||||||
|
d.pop('event')
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NewCharacter(Response):
|
||||||
|
character: dict
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RemoveActor(Response):
|
||||||
|
message: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PublicMessage(Response):
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PrivateMessage(Response):
|
||||||
|
message: str
|
||||||
|
recipient: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DiceRoll(Response):
|
||||||
|
message: str
|
||||||
|
dice: list
|
||||||
|
result: int
|
||||||
|
|
||||||
|
|
||||||
|
def build_dice_roll(sender, message, dice, result):
|
||||||
|
return DiceRoll(sender, Events.DICE_ROLL, message, dice, result)
|
||||||
|
|
||||||
|
|
||||||
|
def build_new_character(sender, char_dict):
|
||||||
|
return NewCharacter(sender, Events.NEW_CHARACTER, char_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def build_remove_actor(sender, message, name):
|
||||||
|
return RemoveActor(sender, Events.REMOVE_ACTOR, message, name)
|
||||||
|
|
||||||
|
|
||||||
|
def build_private_message(sender, message, recipient):
|
||||||
|
return PrivateMessage(sender, Events.PRIVATE_CHAT, message, recipient)
|
||||||
|
|
||||||
|
|
||||||
|
def build_public_message(sender, message):
|
||||||
|
return PublicMessage(sender, Events.PUBLIC_CHAT, message)
|
||||||
|
|
||||||
|
|
||||||
|
def build_system_message(user, message):
|
||||||
|
return PrivateMessage('System', Events.SYSTEM_MESSAGE, message, user)
|
||||||
14
events.py
Normal file
14
events.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class Events(Enum):
|
||||||
|
PUBLIC_CHAT = 'public message'
|
||||||
|
PRIVATE_CHAT = 'private message'
|
||||||
|
SYSTEM_MESSAGE = 'system message'
|
||||||
|
DICE_ROLL = 'dice roll'
|
||||||
|
NEW_CHARACTER = 'new character'
|
||||||
|
REMOVE_ACTOR = 'remove actor'
|
||||||
|
ACTOR_ADDED = 'actor added'
|
||||||
|
USER_EDITED = 'user edited'
|
||||||
|
USER_ADDED = 'user added'
|
||||||
|
USER_REMOVED = 'user removed'
|
||||||
|
|
||||||
66
main.py
Executable file
66
main.py
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# WS server example
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
import socketio
|
||||||
|
|
||||||
|
from actors import ActorManager
|
||||||
|
from events import Events
|
||||||
|
|
||||||
|
from chat.chat_commands import CommandHandler
|
||||||
|
from users.users import UserManager
|
||||||
|
|
||||||
|
|
||||||
|
sio = socketio.Server()
|
||||||
|
app = socketio.WSGIApp(sio)
|
||||||
|
actor_manager = ActorManager()
|
||||||
|
command_handler = CommandHandler()
|
||||||
|
user_manager = UserManager()
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on('connect')
|
||||||
|
def connect(sid, environ):
|
||||||
|
print("CONNECTED: {}".format(sid))
|
||||||
|
for user in user_manager.get_users():
|
||||||
|
sio.emit(Events.USER_ADDED.value, user, room=sid)
|
||||||
|
|
||||||
|
@sio.on('disconnect')
|
||||||
|
def disconnect(sid):
|
||||||
|
print("DISCONNECTED: {}".format(sid))
|
||||||
|
name = user_manager.remove_user(sid)
|
||||||
|
sio.emit(Events.USER_REMOVED.value, name)
|
||||||
|
|
||||||
|
@sio.on(Events.PUBLIC_CHAT.value)
|
||||||
|
def message(sid, data):
|
||||||
|
# print(data)
|
||||||
|
response = command_handler.handle(data['char'], data['message'])
|
||||||
|
# print(response)
|
||||||
|
# print("\n")
|
||||||
|
sio.emit(response.event.
|
||||||
|
value, response.to_json())
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on(Events.NEW_CHARACTER.value)
|
||||||
|
def message(sid, data):
|
||||||
|
print("INCOMING\n", data)
|
||||||
|
actor_manager.add_character(data)
|
||||||
|
sio.emit(Events.NEW_CHARACTER.value, data)
|
||||||
|
|
||||||
|
@sio.on(Events.USER_EDITED.value)
|
||||||
|
def message(sid, data):
|
||||||
|
print("USER EDITED: ", sid, data)
|
||||||
|
if user_manager.has_user(data['old']):
|
||||||
|
user_manager.remove_user(sid)
|
||||||
|
sio.emit(Events.USER_REMOVED.value, data['old'])
|
||||||
|
user_manager.add_user(data['new']['characterName'], sid)
|
||||||
|
d = data['new']
|
||||||
|
d['characterLoaded'] = True
|
||||||
|
d['skills'] = 'Akrobatik\nArkane Kunde\nSeefahrt'
|
||||||
|
sio.emit(Events.USER_EDITED.value, d, room=sid)
|
||||||
|
sio.emit(Events.USER_ADDED.value, data['new']['characterName'])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
eventlet.wsgi.server(eventlet.listen(('', 3101)), app)
|
||||||
|
|
||||||
0
users/__init__.py
Normal file
0
users/__init__.py
Normal file
0
users/sheet_reader.py
Normal file
0
users/sheet_reader.py
Normal file
24
users/users.py
Normal file
24
users/users.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import chat.response as response
|
||||||
|
|
||||||
|
|
||||||
|
class UserManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.users = dict()
|
||||||
|
self.skill_map = dict()
|
||||||
|
|
||||||
|
def add_user(self, name, sid):
|
||||||
|
self.users[sid] = name
|
||||||
|
print("ADDED USER, USERS NOW IS:", self.users)
|
||||||
|
return name
|
||||||
|
|
||||||
|
def remove_user(self, sid):
|
||||||
|
print("REMOVING USER:", sid)
|
||||||
|
if sid in self.users:
|
||||||
|
return self.users.pop(sid)
|
||||||
|
|
||||||
|
def has_user(self, name):
|
||||||
|
return name in self.users.values()
|
||||||
|
|
||||||
|
def get_users(self):
|
||||||
|
return self.users.values()
|
||||||
|
|
||||||
Reference in New Issue
Block a user