From fb53a2e1546cbac262a614be8e7b3408e75e0598 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Fri, 5 Aug 2022 16:28:09 -0400 Subject: [PATCH 01/13] Add basic bbcode support for godot integration --- .../base_systems/godotwebsocket/__init__.py | 0 .../godotwebsocket/test_text2bbcode.py | 84 +++ .../godotwebsocket/text2bbcode.py | 540 ++++++++++++++++++ .../base_systems/godotwebsocket/webclient.py | 83 +++ 4 files changed, 707 insertions(+) create mode 100644 evennia/contrib/base_systems/godotwebsocket/__init__.py create mode 100644 evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py create mode 100644 evennia/contrib/base_systems/godotwebsocket/text2bbcode.py create mode 100644 evennia/contrib/base_systems/godotwebsocket/webclient.py diff --git a/evennia/contrib/base_systems/godotwebsocket/__init__.py b/evennia/contrib/base_systems/godotwebsocket/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py new file mode 100644 index 0000000000..a1ad6434bb --- /dev/null +++ b/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py @@ -0,0 +1,84 @@ +"""Tests for text2html """ + +from django.test import TestCase +from evennia.utils import ansi +from evennia.contrib.base_systems.godotwebsocket import text2bbcode +import mock + + +class TestText2Html(TestCase): + def test_format_styles(self): + parser = text2bbcode.BBCODE_PARSER + self.assertEqual("foo", parser.format_styles("foo")) + self.assertEqual( + '[color=#800000]red[/color]foo', + parser.format_styles( + ansi.ANSI_UNHILITE + ansi.ANSI_RED + "red" + ansi.ANSI_NORMAL + "foo" + ), + ) + self.assertEqual( + '[bgcolor=#800000]red[/bgcolor]foo', + parser.format_styles(ansi.ANSI_BACK_RED + "red" + ansi.ANSI_NORMAL + "foo"), + ) + self.assertEqual( + '[bgcolor=#800000][color=#008000]red[/color][/bgcolor]foo', + parser.format_styles( + ansi.ANSI_BACK_RED + + ansi.ANSI_UNHILITE + + ansi.ANSI_GREEN + + "red" + + ansi.ANSI_NORMAL + + "foo" + ), + ) + self.assertEqual( + 'a [u]red[/u]foo', + parser.format_styles("a " + ansi.ANSI_UNDERLINE + "red" + ansi.ANSI_NORMAL + "foo"), + ) + self.assertEqual( + 'a [blink]red[/blink]foo', + parser.format_styles("a " + ansi.ANSI_BLINK + "red" + ansi.ANSI_NORMAL + "foo"), + ) + self.assertEqual( + 'a [bgcolor=#c0c0c0][color=#000000]red[/color][/bgcolor]foo', + parser.format_styles("a " + ansi.ANSI_INVERSE + "red" + ansi.ANSI_NORMAL + "foo"), + ) + + def test_convert_urls(self): + parser = text2bbcode.BBCODE_PARSER + self.assertEqual("foo", parser.convert_urls("foo")) + self.assertEqual( + 'a [url=http://redfoo]http://redfoo[/url] runs', + parser.convert_urls("a http://redfoo runs"), + ) + + def test_sub_mxp_links(self): + parser = text2bbcode.BBCODE_PARSER + mocked_match = mock.Mock() + mocked_match.groups.return_value = ["cmd", "text"] + + self.assertEqual("[mxp=send cmd=cmd]text[/mxp]", parser.sub_mxp_links(mocked_match)) + + def test_sub_text(self): + parser = text2bbcode.BBCODE_PARSER + + mocked_match = mock.Mock() + + mocked_match.groupdict.return_value = {"lineend": "foo"} + self.assertEqual("\n", parser.sub_text(mocked_match)) + + + def test_parse_bbcode(self): + self.assertEqual("foo", text2bbcode.parse_to_bbcode("foo")) + self.maxDiff = None + self.assertEqual( + text2bbcode.parse_to_bbcode("|^|[CHello|n|u|rW|go|yr|bl|md|c!|[G!"), + '[blink][bgcolor=#008080]Hello[/bgcolor][/blink]' + '[u][color=#ff0000]W[/color][/u]' + '[u][color=#00ff00]o[/color][/u]' + '[u][color=#ffff00]r[/color][/u]' + '[u][color=#0000ff]l[/color][/u]' + '[u][color=#ff00ff]d[/color][/u]' + '[u][color=#00ffff]![/color][/u]' + '[u][bgcolor=#008000][color=#00ffff]![/color][/bgcolor][/u]', + ) diff --git a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py new file mode 100644 index 0000000000..eba1838c7b --- /dev/null +++ b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py @@ -0,0 +1,540 @@ +""" +ANSI -> html converter + +Credit for original idea and implementation +goes to Muhammad Alkarouri and his +snippet #577349 on http://code.activestate.com. + +(extensively modified by Griatch 2010) +""" + +from evennia.utils.text2html import TextToHTMLparser + +from evennia.utils.ansi import * + +# All xterm256 RGB equivalents + +XTERM256_FG = "\033[38;5;{}m" +XTERM256_BG = "\033[48;5;{}m" + +COLOR_INDICE_TO_HEX = {'color-000': '#000000', 'color-001': '#800000', 'color-002': '#008000', 'color-003': '#808000', + 'color-004': '#000080', 'color-005': '#800080', 'color-006': '#008080', 'color-007': '#c0c0c0', + 'color-008': '#808080', 'color-009': '#ff0000', 'color-010': '#00ff00', 'color-011': '#ffff00', + 'color-012': '#0000ff', 'color-013': '#ff00ff', 'color-014': '#00ffff', 'color-015': '#ffffff', + 'color-016': '#000000', 'color-017': '#00005f', 'color-018': '#000087', 'color-019': '#0000af', + 'color-020': '#0000df', 'color-021': '#0000ff', 'color-022': '#005f00', 'color-023': '#005f5f', + 'color-024': '#005f87', 'color-025': '#005faf', 'color-026': '#005fdf', 'color-027': '#005fff', + 'color-028': '#008700', 'color-029': '#00875f', 'color-030': '#008787', 'color-031': '#0087af', + 'color-032': '#0087df', 'color-033': '#0087ff', 'color-034': '#00af00', 'color-035': '#00af5f', + 'color-036': '#00af87', 'color-037': '#00afaf', 'color-038': '#00afdf', 'color-039': '#00afff', + 'color-040': '#00df00', 'color-041': '#00df5f', 'color-042': '#00df87', 'color-043': '#00dfaf', + 'color-044': '#00dfdf', 'color-045': '#00dfff', 'color-046': '#00ff00', 'color-047': '#00ff5f', + 'color-048': '#00ff87', 'color-049': '#00ffaf', 'color-050': '#00ffdf', 'color-051': '#00ffff', + 'color-052': '#5f0000', 'color-053': '#5f005f', 'color-054': '#5f0087', 'color-055': '#5f00af', + 'color-056': '#5f00df', 'color-057': '#5f00ff', 'color-058': '#5f5f00', 'color-059': '#5f5f5f', + 'color-060': '#5f5f87', 'color-061': '#5f5faf', 'color-062': '#5f5fdf', 'color-063': '#5f5fff', + 'color-064': '#5f8700', 'color-065': '#5f875f', 'color-066': '#5f8787', 'color-067': '#5f87af', + 'color-068': '#5f87df', 'color-069': '#5f87ff', 'color-070': '#5faf00', 'color-071': '#5faf5f', + 'color-072': '#5faf87', 'color-073': '#5fafaf', 'color-074': '#5fafdf', 'color-075': '#5fafff', + 'color-076': '#5fdf00', 'color-077': '#5fdf5f', 'color-078': '#5fdf87', 'color-079': '#5fdfaf', + 'color-080': '#5fdfdf', 'color-081': '#5fdfff', 'color-082': '#5fff00', 'color-083': '#5fff5f', + 'color-084': '#5fff87', 'color-085': '#5fffaf', 'color-086': '#5fffdf', 'color-087': '#5fffff', + 'color-088': '#870000', 'color-089': '#87005f', 'color-090': '#870087', 'color-091': '#8700af', + 'color-092': '#8700df', 'color-093': '#8700ff', 'color-094': '#875f00', 'color-095': '#875f5f', + 'color-096': '#875f87', 'color-097': '#875faf', 'color-098': '#875fdf', 'color-099': '#875fff', + 'color-100': '#878700', 'color-101': '#87875f', 'color-102': '#878787', 'color-103': '#8787af', + 'color-104': '#8787df', 'color-105': '#8787ff', 'color-106': '#87af00', 'color-107': '#87af5f', + 'color-108': '#87af87', 'color-109': '#87afaf', 'color-110': '#87afdf', 'color-111': '#87afff', + 'color-112': '#87df00', 'color-113': '#87df5f', 'color-114': '#87df87', 'color-115': '#87dfaf', + 'color-116': '#87dfdf', 'color-117': '#87dfff', 'color-118': '#87ff00', 'color-119': '#87ff5f', + 'color-120': '#87ff87', 'color-121': '#87ffaf', 'color-122': '#87ffdf', 'color-123': '#87ffff', + 'color-124': '#af0000', 'color-125': '#af005f', 'color-126': '#af0087', 'color-127': '#af00af', + 'color-128': '#af00df', 'color-129': '#af00ff', 'color-130': '#af5f00', 'color-131': '#af5f5f', + 'color-132': '#af5f87', 'color-133': '#af5faf', 'color-134': '#af5fdf', 'color-135': '#af5fff', + 'color-136': '#af8700', 'color-137': '#af875f', 'color-138': '#af8787', 'color-139': '#af87af', + 'color-140': '#af87df', 'color-141': '#af87ff', 'color-142': '#afaf00', 'color-143': '#afaf5f', + 'color-144': '#afaf87', 'color-145': '#afafaf', 'color-146': '#afafdf', 'color-147': '#afafff', + 'color-148': '#afdf00', 'color-149': '#afdf5f', 'color-150': '#afdf87', 'color-151': '#afdfaf', + 'color-152': '#afdfdf', 'color-153': '#afdfff', 'color-154': '#afff00', 'color-155': '#afff5f', + 'color-156': '#afff87', 'color-157': '#afffaf', 'color-158': '#afffdf', 'color-159': '#afffff', + 'color-160': '#df0000', 'color-161': '#df005f', 'color-162': '#df0087', 'color-163': '#df00af', + 'color-164': '#df00df', 'color-165': '#df00ff', 'color-166': '#df5f00', 'color-167': '#df5f5f', + 'color-168': '#df5f87', 'color-169': '#df5faf', 'color-170': '#df5fdf', 'color-171': '#df5fff', + 'color-172': '#df8700', 'color-173': '#df875f', 'color-174': '#df8787', 'color-175': '#df87af', + 'color-176': '#df87df', 'color-177': '#df87ff', 'color-178': '#dfaf00', 'color-179': '#dfaf5f', + 'color-180': '#dfaf87', 'color-181': '#dfafaf', 'color-182': '#dfafdf', 'color-183': '#dfafff', + 'color-184': '#dfdf00', 'color-185': '#dfdf5f', 'color-186': '#dfdf87', 'color-187': '#dfdfaf', + 'color-188': '#dfdfdf', 'color-189': '#dfdfff', 'color-190': '#dfff00', 'color-191': '#dfff5f', + 'color-192': '#dfff87', 'color-193': '#dfffaf', 'color-194': '#dfffdf', 'color-195': '#dfffff', + 'color-196': '#ff0000', 'color-197': '#ff005f', 'color-198': '#ff0087', 'color-199': '#ff00af', + 'color-200': '#ff00df', 'color-201': '#ff00ff', 'color-202': '#ff5f00', 'color-203': '#ff5f5f', + 'color-204': '#ff5f87', 'color-205': '#ff5faf', 'color-206': '#ff5fdf', 'color-207': '#ff5fff', + 'color-208': '#ff8700', 'color-209': '#ff875f', 'color-210': '#ff8787', 'color-211': '#ff87af', + 'color-212': '#ff87df', 'color-213': '#ff87ff', 'color-214': '#ffaf00', 'color-215': '#ffaf5f', + 'color-216': '#ffaf87', 'color-217': '#ffafaf', 'color-218': '#ffafdf', 'color-219': '#ffafff', + 'color-220': '#ffdf00', 'color-221': '#ffdf5f', 'color-222': '#ffdf87', 'color-223': '#ffdfaf', + 'color-224': '#ffdfdf', 'color-225': '#ffdfff', 'color-226': '#ffff00', 'color-227': '#ffff5f', + 'color-228': '#ffff87', 'color-229': '#ffffaf', 'color-230': '#ffffdf', 'color-231': '#ffffff', + 'color-232': '#080808', 'color-233': '#121212', 'color-234': '#1c1c1c', 'color-235': '#262626', + 'color-236': '#303030', 'color-237': '#3a3a3a', 'color-238': '#444444', 'color-239': '#4e4e4e', + 'color-240': '#585858', 'color-241': '#606060', 'color-242': '#666666', 'color-243': '#767676', + 'color-244': '#808080', 'color-245': '#8a8a8a', 'color-246': '#949494', 'color-247': '#9e9e9e', + 'color-248': '#a8a8a8', 'color-249': '#b2b2b2', 'color-250': '#bcbcbc', 'color-251': '#c6c6c6', + 'color-252': '#d0d0d0', 'color-253': '#dadada', 'color-254': '#e4e4e4', 'color-255': '#eeeeee', + 'bgcolor-000': '#000000', 'bgcolor-001': '#800000', 'bgcolor-002': '#008000', + 'bgcolor-003': '#808000', 'bgcolor-004': '#000080', 'bgcolor-005': '#800080', + 'bgcolor-006': '#008080', 'bgcolor-007': '#c0c0c0', 'bgcolor-008': '#808080', + 'bgcolor-009': '#ff0000', 'bgcolor-010': '#00ff00', 'bgcolor-011': '#ffff00', + 'bgcolor-012': '#0000ff', 'bgcolor-013': '#ff00ff', 'bgcolor-014': '#00ffff', + 'bgcolor-015': '#ffffff', 'bgcolor-016': '#000000', 'bgcolor-017': '#00005f', + 'bgcolor-018': '#000087', 'bgcolor-019': '#0000af', 'bgcolor-020': '#0000df', + 'bgcolor-021': '#0000ff', 'bgcolor-022': '#005f00', 'bgcolor-023': '#005f5f', + 'bgcolor-024': '#005f87', 'bgcolor-025': '#005faf', 'bgcolor-026': '#005fdf', + 'bgcolor-027': '#005fff', 'bgcolor-028': '#008700', 'bgcolor-029': '#00875f', + 'bgcolor-030': '#008787', 'bgcolor-031': '#0087af', 'bgcolor-032': '#0087df', + 'bgcolor-033': '#0087ff', 'bgcolor-034': '#00af00', 'bgcolor-035': '#00af5f', + 'bgcolor-036': '#00af87', 'bgcolor-037': '#00afaf', 'bgcolor-038': '#00afdf', + 'bgcolor-039': '#00afff', 'bgcolor-040': '#00df00', 'bgcolor-041': '#00df5f', + 'bgcolor-042': '#00df87', 'bgcolor-043': '#00dfaf', 'bgcolor-044': '#00dfdf', + 'bgcolor-045': '#00dfff', 'bgcolor-046': '#00ff00', 'bgcolor-047': '#00ff5f', + 'bgcolor-048': '#00ff87', 'bgcolor-049': '#00ffaf', 'bgcolor-050': '#00ffdf', + 'bgcolor-051': '#00ffff', 'bgcolor-052': '#5f0000', 'bgcolor-053': '#5f005f', + 'bgcolor-054': '#5f0087', 'bgcolor-055': '#5f00af', 'bgcolor-056': '#5f00df', + 'bgcolor-057': '#5f00ff', 'bgcolor-058': '#5f5f00', 'bgcolor-059': '#5f5f5f', + 'bgcolor-060': '#5f5f87', 'bgcolor-061': '#5f5faf', 'bgcolor-062': '#5f5fdf', + 'bgcolor-063': '#5f5fff', 'bgcolor-064': '#5f8700', 'bgcolor-065': '#5f875f', + 'bgcolor-066': '#5f8787', 'bgcolor-067': '#5f87af', 'bgcolor-068': '#5f87df', + 'bgcolor-069': '#5f87ff', 'bgcolor-070': '#5faf00', 'bgcolor-071': '#5faf5f', + 'bgcolor-072': '#5faf87', 'bgcolor-073': '#5fafaf', 'bgcolor-074': '#5fafdf', + 'bgcolor-075': '#5fafff', 'bgcolor-076': '#5fdf00', 'bgcolor-077': '#5fdf5f', + 'bgcolor-078': '#5fdf87', 'bgcolor-079': '#5fdfaf', 'bgcolor-080': '#5fdfdf', + 'bgcolor-081': '#5fdfff', 'bgcolor-082': '#5fff00', 'bgcolor-083': '#5fff5f', + 'bgcolor-084': '#5fff87', 'bgcolor-085': '#5fffaf', 'bgcolor-086': '#5fffdf', + 'bgcolor-087': '#5fffff', 'bgcolor-088': '#870000', 'bgcolor-089': '#87005f', + 'bgcolor-090': '#870087', 'bgcolor-091': '#8700af', 'bgcolor-092': '#8700df', + 'bgcolor-093': '#8700ff', 'bgcolor-094': '#875f00', 'bgcolor-095': '#875f5f', + 'bgcolor-096': '#875f87', 'bgcolor-097': '#875faf', 'bgcolor-098': '#875fdf', + 'bgcolor-099': '#875fff', 'bgcolor-100': '#878700', 'bgcolor-101': '#87875f', + 'bgcolor-102': '#878787', 'bgcolor-103': '#8787af', 'bgcolor-104': '#8787df', + 'bgcolor-105': '#8787ff', 'bgcolor-106': '#87af00', 'bgcolor-107': '#87af5f', + 'bgcolor-108': '#87af87', 'bgcolor-109': '#87afaf', 'bgcolor-110': '#87afdf', + 'bgcolor-111': '#87afff', 'bgcolor-112': '#87df00', 'bgcolor-113': '#87df5f', + 'bgcolor-114': '#87df87', 'bgcolor-115': '#87dfaf', 'bgcolor-116': '#87dfdf', + 'bgcolor-117': '#87dfff', 'bgcolor-118': '#87ff00', 'bgcolor-119': '#87ff5f', + 'bgcolor-120': '#87ff87', 'bgcolor-121': '#87ffaf', 'bgcolor-122': '#87ffdf', + 'bgcolor-123': '#87ffff', 'bgcolor-124': '#af0000', 'bgcolor-125': '#af005f', + 'bgcolor-126': '#af0087', 'bgcolor-127': '#af00af', 'bgcolor-128': '#af00df', + 'bgcolor-129': '#af00ff', 'bgcolor-130': '#af5f00', 'bgcolor-131': '#af5f5f', + 'bgcolor-132': '#af5f87', 'bgcolor-133': '#af5faf', 'bgcolor-134': '#af5fdf', + 'bgcolor-135': '#af5fff', 'bgcolor-136': '#af8700', 'bgcolor-137': '#af875f', + 'bgcolor-138': '#af8787', 'bgcolor-139': '#af87af', 'bgcolor-140': '#af87df', + 'bgcolor-141': '#af87ff', 'bgcolor-142': '#afaf00', 'bgcolor-143': '#afaf5f', + 'bgcolor-144': '#afaf87', 'bgcolor-145': '#afafaf', 'bgcolor-146': '#afafdf', + 'bgcolor-147': '#afafff', 'bgcolor-148': '#afdf00', 'bgcolor-149': '#afdf5f', + 'bgcolor-150': '#afdf87', 'bgcolor-151': '#afdfaf', 'bgcolor-152': '#afdfdf', + 'bgcolor-153': '#afdfff', 'bgcolor-154': '#afff00', 'bgcolor-155': '#afff5f', + 'bgcolor-156': '#afff87', 'bgcolor-157': '#afffaf', 'bgcolor-158': '#afffdf', + 'bgcolor-159': '#afffff', 'bgcolor-160': '#df0000', 'bgcolor-161': '#df005f', + 'bgcolor-162': '#df0087', 'bgcolor-163': '#df00af', 'bgcolor-164': '#df00df', + 'bgcolor-165': '#df00ff', 'bgcolor-166': '#df5f00', 'bgcolor-167': '#df5f5f', + 'bgcolor-168': '#df5f87', 'bgcolor-169': '#df5faf', 'bgcolor-170': '#df5fdf', + 'bgcolor-171': '#df5fff', 'bgcolor-172': '#df8700', 'bgcolor-173': '#df875f', + 'bgcolor-174': '#df8787', 'bgcolor-175': '#df87af', 'bgcolor-176': '#df87df', + 'bgcolor-177': '#df87ff', 'bgcolor-178': '#dfaf00', 'bgcolor-179': '#dfaf5f', + 'bgcolor-180': '#dfaf87', 'bgcolor-181': '#dfafaf', 'bgcolor-182': '#dfafdf', + 'bgcolor-183': '#dfafff', 'bgcolor-184': '#dfdf00', 'bgcolor-185': '#dfdf5f', + 'bgcolor-186': '#dfdf87', 'bgcolor-187': '#dfdfaf', 'bgcolor-188': '#dfdfdf', + 'bgcolor-189': '#dfdfff', 'bgcolor-190': '#dfff00', 'bgcolor-191': '#dfff5f', + 'bgcolor-192': '#dfff87', 'bgcolor-193': '#dfffaf', 'bgcolor-194': '#dfffdf', + 'bgcolor-195': '#dfffff', 'bgcolor-196': '#ff0000', 'bgcolor-197': '#ff005f', + 'bgcolor-198': '#ff0087', 'bgcolor-199': '#ff00af', 'bgcolor-200': '#ff00df', + 'bgcolor-201': '#ff00ff', 'bgcolor-202': '#ff5f00', 'bgcolor-203': '#ff5f5f', + 'bgcolor-204': '#ff5f87', 'bgcolor-205': '#ff5faf', 'bgcolor-206': '#ff5fdf', + 'bgcolor-207': '#ff5fff', 'bgcolor-208': '#ff8700', 'bgcolor-209': '#ff875f', + 'bgcolor-210': '#ff8787', 'bgcolor-211': '#ff87af', 'bgcolor-212': '#ff87df', + 'bgcolor-213': '#ff87ff', 'bgcolor-214': '#ffaf00', 'bgcolor-215': '#ffaf5f', + 'bgcolor-216': '#ffaf87', 'bgcolor-217': '#ffafaf', 'bgcolor-218': '#ffafdf', + 'bgcolor-219': '#ffafff', 'bgcolor-220': '#ffdf00', 'bgcolor-221': '#ffdf5f', + 'bgcolor-222': '#ffdf87', 'bgcolor-223': '#ffdfaf', 'bgcolor-224': '#ffdfdf', + 'bgcolor-225': '#ffdfff', 'bgcolor-226': '#ffff00', 'bgcolor-227': '#ffff5f', + 'bgcolor-228': '#ffff87', 'bgcolor-229': '#ffffaf', 'bgcolor-230': '#ffffdf', + 'bgcolor-231': '#ffffff', 'bgcolor-232': '#080808', 'bgcolor-233': '#121212', + 'bgcolor-234': '#1c1c1c', 'bgcolor-235': '#262626', 'bgcolor-236': '#303030', + 'bgcolor-237': '#3a3a3a', 'bgcolor-238': '#444444', 'bgcolor-239': '#4e4e4e', + 'bgcolor-240': '#585858', 'bgcolor-241': '#606060', 'bgcolor-242': '#666666', + 'bgcolor-243': '#767676', 'bgcolor-244': '#808080', 'bgcolor-245': '#8a8a8a', + 'bgcolor-246': '#949494', 'bgcolor-247': '#9e9e9e', 'bgcolor-248': '#a8a8a8', + 'bgcolor-249': '#b2b2b2', 'bgcolor-250': '#bcbcbc', 'bgcolor-251': '#c6c6c6', + 'bgcolor-252': '#d0d0d0', 'bgcolor-253': '#dadada', 'bgcolor-254': '#e4e4e4', + 'bgcolor-255': '#eeeeee'} + + +class RootTag(object): + __slots__ = ('child',) + + def __init__(self): + self.child = None + + def __str__(self): + return str(self.child) if self.child else "" + + +class ChildTag(object): + def __init__(self, parent): + self.parent = parent + if parent: + parent.child = self + + def set_parent(self, parent): + self.parent = parent + if parent: + parent.child = self + + +class TextTag(ChildTag): + __slots__ = ('parent', 'child', 'text') + + def __init__(self, parent, text): + super().__init__(parent) + self.text = text + self.child = None + + def __str__(self): + return f"{self.text}{self.child or ''}" + + +class BBCodeTag(ChildTag): + __slots__ = ('parent', 'child',) + + code = '' + + def __init__(self, parent): + super().__init__(parent) + self.child = None + + def __str__(self): + return f"[{self.code}]{self.child or ''}[/{self.code}]" + + +class UnderlineTag(BBCodeTag): + code = 'u' + + +class BlinkTag(BBCodeTag): + code = 'blink' + + +class ColorTag(BBCodeTag): + __slots__ = ('parent', 'child', 'color_hex',) + + code = 'color' + + def __init__(self, parent, color_hex): + super().__init__(parent) + self.color_hex = color_hex + + def __str__(self): + return f"[{self.code}={self.color_hex}]{self.child or ''}[/{self.code}]" + + +class BGColorTag(ColorTag): + code = 'bgcolor' + + +class UrlTag(BBCodeTag): + __slots__ = ('parent', 'child', 'url_data',) + + code = 'url' + + def __init__(self, parent, url_data=''): + super().__init__(parent) + self.url_data = url_data + + def __str__(self): + return f"[{self.code}={self.url_data}]{self.child or ''}[/{self.code}]" + + +class TextToBBCODEparser(TextToHTMLparser): + """ + This class describes a parser for converting from ANSI to html. + """ + + def convert_urls(self, text): + # Converts to bbcode styled urls + return self.re_url.sub(r'[url=\1]\1[/url]\2', text) + + def sub_mxp_links(self, match): + """ + Helper method to be passed to re.sub, + replaces MXP links with HTML code. + + Args: + match (re.Matchobject): Match for substitution. + + Returns: + text (str): Processed text. + + """ + cmd, text = [grp.replace('"', "\\"") for grp in match.groups()] + val = f"[mxp=send cmd={cmd}]{text}[/mxp]" + + return val + + def sub_mxp_urls(self, match): + """ + Helper method to be passed to re.sub, + replaces MXP links with HTML code. + Args: + match (re.Matchobject): Match for substitution. + Returns: + text (str): Processed text. + """ + url, text = [grp.replace('"', "\\"") for grp in match.groups()] + val = r"""{text}""".format( + url=url, text=text + ) + return val + + def sub_text(self, match): + """ + Helper method to be passed to re.sub, + for handling all substitutions. + + Args: + match (re.Matchobject): Match for substitution. + + Returns: + text (str): Processed text. + + """ + cdict = match.groupdict() + if cdict["lineend"]: + return "\n" + + return None + + def format_styles(self, text): + """ + Takes a string with parsed ANSI codes and replaces them with + HTML spans and CSS classes. + + Args: + text (str): The string to process. + + Returns: + text (str): Processed text. + """ + + # split out the ANSI codes and clean out any empty items + str_list = [substr for substr in self.re_style.split(text) if substr] + + inverse = False + # default color is light grey - unhilite + white + hilight = ANSI_UNHILITE + fg = ANSI_WHITE + # default bg is black + bg = ANSI_BACK_BLACK + previous_fg = None + previous_bg = None + blink = False + underline = False + new_style = False + + parts = [] + root_tag = RootTag() + current_tag = root_tag + + for i, substr in enumerate(str_list): + # reset all current styling + if substr == ANSI_NORMAL: + # close any existing span if necessary + parts.append(str(root_tag)) + root_tag = RootTag() + current_tag = root_tag + # reset to defaults + inverse = False + hilight = ANSI_UNHILITE + fg = ANSI_WHITE + bg = ANSI_BACK_BLACK + previous_fg = None + previous_bg = None + blink = False + underline = False + new_style = False + + # change color + elif substr in self.ansi_color_codes + self.xterm_fg_codes: + # set new color + fg = substr + new_style = True + + # change bg color + elif substr in self.ansi_bg_codes + self.xterm_bg_codes: + # set new bg + bg = substr + new_style = True + + # non-color codes + elif substr in self.style_codes: + new_style = True + + # hilight codes + if substr in (ANSI_HILITE, ANSI_UNHILITE, ANSI_INV_HILITE, ANSI_INV_BLINK_HILITE): + # set new hilight status + hilight = ANSI_UNHILITE if substr == ANSI_UNHILITE else ANSI_HILITE + + # inversion codes + if substr in (ANSI_INVERSE, ANSI_INV_HILITE, ANSI_INV_BLINK_HILITE): + inverse = True + + # blink codes + if ( + substr in (ANSI_BLINK, ANSI_BLINK_HILITE, ANSI_INV_BLINK_HILITE) + and not blink + ): + blink = True + current_tag = BlinkTag(current_tag) + + # underline + if substr == ANSI_UNDERLINE and not underline: + underline = True + current_tag = UnderlineTag(current_tag) + + else: + close_tags = False + color_tag = None + bgcolor_tag = None + # normal text, add text back to list + if new_style: + # prior entry was cleared, which means style change + # get indices for the fg and bg codes + bg_index = self.bglist.index(bg) + try: + color_index = self.colorlist.index(hilight + fg) + except ValueError: + # xterm256 colors don't have the hilight codes + color_index = self.colorlist.index(fg) + + if inverse: + # inverse means swap fg and bg indices + bg_class = "bgcolor-{}".format(str(color_index).rjust(3, "0")) + color_class = "color-{}".format(str(bg_index).rjust(3, "0")) + else: + # use fg and bg indices for classes + bg_class = "bgcolor-{}".format(str(bg_index).rjust(3, "0")) + color_class = "color-{}".format(str(color_index).rjust(3, "0")) + + # black bg is the default, don't explicitly style + if bg_class != "bgcolor-000": + color_hex = COLOR_INDICE_TO_HEX.get(bg_class) + bgcolor_tag = BGColorTag(None, color_hex=color_hex) + if previous_bg and previous_bg != color_hex: + close_tags = True + else: + previous_bg = color_hex + + # light grey text is the default, don't explicitly style + if color_class != "color-007": + color_hex = COLOR_INDICE_TO_HEX.get(color_class) + color_tag = ColorTag(None, color_hex=color_hex) + if previous_fg and previous_fg != color_hex: + close_tags = True + else: + previous_fg = color_hex + + new_tag = TextTag(None, substr) + if close_tags: + # Because the order is important, we need to close the tags and reopen those who shouldn't reset. + new_style = False + parts.append(str(root_tag)) + root_tag = RootTag() + current_tag = root_tag + if blink: + current_tag = BlinkTag(current_tag) + + if underline: + current_tag = UnderlineTag(current_tag) + + if bgcolor_tag: + bgcolor_tag.set_parent(current_tag) + current_tag = bgcolor_tag + + if color_tag: + color_tag.set_parent(current_tag) + current_tag = color_tag + + new_tag.set_parent(current_tag) + current_tag = new_tag + else: + if bgcolor_tag: + bgcolor_tag.set_parent(current_tag) + current_tag = bgcolor_tag + + if color_tag: + color_tag.set_parent(current_tag) + current_tag = color_tag + + new_tag.set_parent(current_tag) + current_tag = new_tag + + any_text = self._get_text_tag(root_tag) + if any_text: + # Only append tags if text was added. + last_part = str(root_tag) + parts.append(last_part) + + # recombine back into string + return "".join(parts) + + def _get_text_tag(self, root): + child = root.child + while child: + if isinstance(child, TextTag): + return child + else: + child = child.child + + return None + + def parse(self, text, strip_ansi=False): + """ + Main access function, converts a text containing ANSI codes + into html statements. + + Args: + text (str): Text to process. + strip_ansi (bool, optional): + + Returns: + text (str): Parsed text. + + """ + # parse everything to ansi first + text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=True, mxp=True) + # convert all ansi to html + result = re.sub(self.re_string, self.sub_text, text) + result = re.sub(self.re_mxplink, self.sub_mxp_links, result) + result = re.sub(self.re_mxpurl, self.sub_mxp_urls, result) + result = self.remove_bells(result) + result = self.format_styles(result) + result = self.remove_backspaces(result) + result = self.convert_urls(result) + # clean out eventual ansi that was missed + ## result = parse_ansi(result, strip_ansi=True) + + return result + + +BBCODE_PARSER = TextToBBCODEparser() + + +# +# Access function +# + + +def parse_to_bbcode(string, strip_ansi=False, parser=BBCODE_PARSER): + """ + Parses a string, replace ANSI markup with html + """ + return parser.parse(string, strip_ansi=strip_ansi) diff --git a/evennia/contrib/base_systems/godotwebsocket/webclient.py b/evennia/contrib/base_systems/godotwebsocket/webclient.py new file mode 100644 index 0000000000..a6be1887f3 --- /dev/null +++ b/evennia/contrib/base_systems/godotwebsocket/webclient.py @@ -0,0 +1,83 @@ +import json + +from autobahn.twisted import WebSocketServerFactory +from twisted.application import internet + +from evennia import settings +from evennia.contrib.base_systems.godotwebsocket.text2bbcode import parse_to_bbcode +from evennia.server.portal import webclient +from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS +from evennia.settings_default import LOCKDOWN_MODE + + +class GodotWebSocketClient(webclient.WebSocketClient): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.protocol_key = "godotclient/websocket" + + def send_text(self, *args, **kwargs): + """ + Send text data. This will pre-process the text for + color-replacement, conversion to html etc. + + Args: + text (str): Text to send. + + Keyword Args: + options (dict): Options-dict with the following keys understood: + - raw (bool): No parsing at all (leave ansi-to-html markers unparsed). + - nocolor (bool): Clean out all color. + - screenreader (bool): Use Screenreader mode. + - send_prompt (bool): Send a prompt with parsed html + + """ + if args: + args = list(args) + text = args[0] + if text is None: + return + else: + return + + flags = self.protocol_flags + + options = kwargs.pop("options", {}) + raw = options.get("raw", flags.get("RAW", False)) + client_raw = options.get("client_raw", False) + nocolor = options.get("nocolor", flags.get("NOCOLOR", False)) + screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) + prompt = options.get("send_prompt", False) + + if screenreader: + # screenreader mode cleans up output + text = webclient.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) + text = webclient._RE_SCREENREADER_REGEX.sub("", text) + cmd = "prompt" if prompt else "text" + if raw: + if client_raw: + args[0] = text + else: + args[0] = webclient.html.escape(text) # escape html! + else: + args[0] = parse_to_bbcode(text, strip_ansi=nocolor) + + # send to client on required form [cmdname, args, kwargs] + self.sendLine(json.dumps([cmd, args, kwargs])) + + +def start_plugin_services(portal): + class GodotWebsocket(WebSocketServerFactory): + "Only here for better naming in logs" + pass + + factory = GodotWebsocket() + factory.noisy = False + factory.protocol = GodotWebSocketClient + factory.sessionhandler = PORTAL_SESSIONS + + interface = "127.0.0.1" if LOCKDOWN_MODE else settings.GODOT_CLIENT_WEBSOCKET_CLIENT_INTERFACE + + port = settings.GODOT_CLIENT_WEBSOCKET_PORT + websocket_service = internet.TCPServer(port, factory, interface=interface) + websocket_service.setName("GodotWebSocket%s:%s" % (interface, port)) + portal.services.addService(websocket_service) From 4ec65920afbda88550d4da3ba4c3b57bc797dde0 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Sun, 9 Oct 2022 21:57:23 -0400 Subject: [PATCH 02/13] Fix test case name --- evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py index a1ad6434bb..ebe24c8eab 100644 --- a/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py @@ -6,7 +6,7 @@ from evennia.contrib.base_systems.godotwebsocket import text2bbcode import mock -class TestText2Html(TestCase): +class TestText2Bbcode(TestCase): def test_format_styles(self): parser = text2bbcode.BBCODE_PARSER self.assertEqual("foo", parser.format_styles("foo")) From f8b24cba49c8c03ca097d04b3d9f31578ed54dd7 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Thu, 13 Oct 2022 18:56:46 -0400 Subject: [PATCH 03/13] Docs --- .../base_systems/godotwebsocket/__init__.py | 12 + .../godotwebsocket/test_text2bbcode.py | 9 +- .../godotwebsocket/text2bbcode.py | 335 +++++++++--------- .../base_systems/godotwebsocket/webclient.py | 31 +- 4 files changed, 198 insertions(+), 189 deletions(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/__init__.py b/evennia/contrib/base_systems/godotwebsocket/__init__.py index e69de29bb2..0e485605a0 100644 --- a/evennia/contrib/base_systems/godotwebsocket/__init__.py +++ b/evennia/contrib/base_systems/godotwebsocket/__init__.py @@ -0,0 +1,12 @@ +""" +Godot Websocket - ChrisLR 2022 + +This provides parsing the ansi text to bbcode used by Godot for their RichTextLabel +and also provides the proper portal service to dedicate a port for Godot's Websockets. + +This allows you to connect both the regular webclient and a godot specific webclient. +You can simply connect the resulting text to Godot's RichTextLabel and have the proper display. +You could also pass extra data to this client for advanced functionality. + +See the docs for more information. +""" \ No newline at end of file diff --git a/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py index ebe24c8eab..3b19245d6e 100644 --- a/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/test_text2bbcode.py @@ -1,9 +1,10 @@ -"""Tests for text2html """ +"""Tests for text2bbcode """ -from django.test import TestCase -from evennia.utils import ansi -from evennia.contrib.base_systems.godotwebsocket import text2bbcode import mock +from django.test import TestCase + +from evennia.contrib.base_systems.godotwebsocket import text2bbcode +from evennia.utils import ansi class TestText2Bbcode(TestCase): diff --git a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py index eba1838c7b..664e107ba8 100644 --- a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py @@ -1,172 +1,174 @@ """ -ANSI -> html converter +Godot Websocket - ChrisLR 2022 -Credit for original idea and implementation -goes to Muhammad Alkarouri and his -snippet #577349 on http://code.activestate.com. - -(extensively modified by Griatch 2010) +This file contains the necessary code and data to convert text with color tags to bbcode (For godot) """ - -from evennia.utils.text2html import TextToHTMLparser - from evennia.utils.ansi import * +from evennia.utils.text2html import TextToHTMLparser # All xterm256 RGB equivalents XTERM256_FG = "\033[38;5;{}m" XTERM256_BG = "\033[48;5;{}m" -COLOR_INDICE_TO_HEX = {'color-000': '#000000', 'color-001': '#800000', 'color-002': '#008000', 'color-003': '#808000', - 'color-004': '#000080', 'color-005': '#800080', 'color-006': '#008080', 'color-007': '#c0c0c0', - 'color-008': '#808080', 'color-009': '#ff0000', 'color-010': '#00ff00', 'color-011': '#ffff00', - 'color-012': '#0000ff', 'color-013': '#ff00ff', 'color-014': '#00ffff', 'color-015': '#ffffff', - 'color-016': '#000000', 'color-017': '#00005f', 'color-018': '#000087', 'color-019': '#0000af', - 'color-020': '#0000df', 'color-021': '#0000ff', 'color-022': '#005f00', 'color-023': '#005f5f', - 'color-024': '#005f87', 'color-025': '#005faf', 'color-026': '#005fdf', 'color-027': '#005fff', - 'color-028': '#008700', 'color-029': '#00875f', 'color-030': '#008787', 'color-031': '#0087af', - 'color-032': '#0087df', 'color-033': '#0087ff', 'color-034': '#00af00', 'color-035': '#00af5f', - 'color-036': '#00af87', 'color-037': '#00afaf', 'color-038': '#00afdf', 'color-039': '#00afff', - 'color-040': '#00df00', 'color-041': '#00df5f', 'color-042': '#00df87', 'color-043': '#00dfaf', - 'color-044': '#00dfdf', 'color-045': '#00dfff', 'color-046': '#00ff00', 'color-047': '#00ff5f', - 'color-048': '#00ff87', 'color-049': '#00ffaf', 'color-050': '#00ffdf', 'color-051': '#00ffff', - 'color-052': '#5f0000', 'color-053': '#5f005f', 'color-054': '#5f0087', 'color-055': '#5f00af', - 'color-056': '#5f00df', 'color-057': '#5f00ff', 'color-058': '#5f5f00', 'color-059': '#5f5f5f', - 'color-060': '#5f5f87', 'color-061': '#5f5faf', 'color-062': '#5f5fdf', 'color-063': '#5f5fff', - 'color-064': '#5f8700', 'color-065': '#5f875f', 'color-066': '#5f8787', 'color-067': '#5f87af', - 'color-068': '#5f87df', 'color-069': '#5f87ff', 'color-070': '#5faf00', 'color-071': '#5faf5f', - 'color-072': '#5faf87', 'color-073': '#5fafaf', 'color-074': '#5fafdf', 'color-075': '#5fafff', - 'color-076': '#5fdf00', 'color-077': '#5fdf5f', 'color-078': '#5fdf87', 'color-079': '#5fdfaf', - 'color-080': '#5fdfdf', 'color-081': '#5fdfff', 'color-082': '#5fff00', 'color-083': '#5fff5f', - 'color-084': '#5fff87', 'color-085': '#5fffaf', 'color-086': '#5fffdf', 'color-087': '#5fffff', - 'color-088': '#870000', 'color-089': '#87005f', 'color-090': '#870087', 'color-091': '#8700af', - 'color-092': '#8700df', 'color-093': '#8700ff', 'color-094': '#875f00', 'color-095': '#875f5f', - 'color-096': '#875f87', 'color-097': '#875faf', 'color-098': '#875fdf', 'color-099': '#875fff', - 'color-100': '#878700', 'color-101': '#87875f', 'color-102': '#878787', 'color-103': '#8787af', - 'color-104': '#8787df', 'color-105': '#8787ff', 'color-106': '#87af00', 'color-107': '#87af5f', - 'color-108': '#87af87', 'color-109': '#87afaf', 'color-110': '#87afdf', 'color-111': '#87afff', - 'color-112': '#87df00', 'color-113': '#87df5f', 'color-114': '#87df87', 'color-115': '#87dfaf', - 'color-116': '#87dfdf', 'color-117': '#87dfff', 'color-118': '#87ff00', 'color-119': '#87ff5f', - 'color-120': '#87ff87', 'color-121': '#87ffaf', 'color-122': '#87ffdf', 'color-123': '#87ffff', - 'color-124': '#af0000', 'color-125': '#af005f', 'color-126': '#af0087', 'color-127': '#af00af', - 'color-128': '#af00df', 'color-129': '#af00ff', 'color-130': '#af5f00', 'color-131': '#af5f5f', - 'color-132': '#af5f87', 'color-133': '#af5faf', 'color-134': '#af5fdf', 'color-135': '#af5fff', - 'color-136': '#af8700', 'color-137': '#af875f', 'color-138': '#af8787', 'color-139': '#af87af', - 'color-140': '#af87df', 'color-141': '#af87ff', 'color-142': '#afaf00', 'color-143': '#afaf5f', - 'color-144': '#afaf87', 'color-145': '#afafaf', 'color-146': '#afafdf', 'color-147': '#afafff', - 'color-148': '#afdf00', 'color-149': '#afdf5f', 'color-150': '#afdf87', 'color-151': '#afdfaf', - 'color-152': '#afdfdf', 'color-153': '#afdfff', 'color-154': '#afff00', 'color-155': '#afff5f', - 'color-156': '#afff87', 'color-157': '#afffaf', 'color-158': '#afffdf', 'color-159': '#afffff', - 'color-160': '#df0000', 'color-161': '#df005f', 'color-162': '#df0087', 'color-163': '#df00af', - 'color-164': '#df00df', 'color-165': '#df00ff', 'color-166': '#df5f00', 'color-167': '#df5f5f', - 'color-168': '#df5f87', 'color-169': '#df5faf', 'color-170': '#df5fdf', 'color-171': '#df5fff', - 'color-172': '#df8700', 'color-173': '#df875f', 'color-174': '#df8787', 'color-175': '#df87af', - 'color-176': '#df87df', 'color-177': '#df87ff', 'color-178': '#dfaf00', 'color-179': '#dfaf5f', - 'color-180': '#dfaf87', 'color-181': '#dfafaf', 'color-182': '#dfafdf', 'color-183': '#dfafff', - 'color-184': '#dfdf00', 'color-185': '#dfdf5f', 'color-186': '#dfdf87', 'color-187': '#dfdfaf', - 'color-188': '#dfdfdf', 'color-189': '#dfdfff', 'color-190': '#dfff00', 'color-191': '#dfff5f', - 'color-192': '#dfff87', 'color-193': '#dfffaf', 'color-194': '#dfffdf', 'color-195': '#dfffff', - 'color-196': '#ff0000', 'color-197': '#ff005f', 'color-198': '#ff0087', 'color-199': '#ff00af', - 'color-200': '#ff00df', 'color-201': '#ff00ff', 'color-202': '#ff5f00', 'color-203': '#ff5f5f', - 'color-204': '#ff5f87', 'color-205': '#ff5faf', 'color-206': '#ff5fdf', 'color-207': '#ff5fff', - 'color-208': '#ff8700', 'color-209': '#ff875f', 'color-210': '#ff8787', 'color-211': '#ff87af', - 'color-212': '#ff87df', 'color-213': '#ff87ff', 'color-214': '#ffaf00', 'color-215': '#ffaf5f', - 'color-216': '#ffaf87', 'color-217': '#ffafaf', 'color-218': '#ffafdf', 'color-219': '#ffafff', - 'color-220': '#ffdf00', 'color-221': '#ffdf5f', 'color-222': '#ffdf87', 'color-223': '#ffdfaf', - 'color-224': '#ffdfdf', 'color-225': '#ffdfff', 'color-226': '#ffff00', 'color-227': '#ffff5f', - 'color-228': '#ffff87', 'color-229': '#ffffaf', 'color-230': '#ffffdf', 'color-231': '#ffffff', - 'color-232': '#080808', 'color-233': '#121212', 'color-234': '#1c1c1c', 'color-235': '#262626', - 'color-236': '#303030', 'color-237': '#3a3a3a', 'color-238': '#444444', 'color-239': '#4e4e4e', - 'color-240': '#585858', 'color-241': '#606060', 'color-242': '#666666', 'color-243': '#767676', - 'color-244': '#808080', 'color-245': '#8a8a8a', 'color-246': '#949494', 'color-247': '#9e9e9e', - 'color-248': '#a8a8a8', 'color-249': '#b2b2b2', 'color-250': '#bcbcbc', 'color-251': '#c6c6c6', - 'color-252': '#d0d0d0', 'color-253': '#dadada', 'color-254': '#e4e4e4', 'color-255': '#eeeeee', - 'bgcolor-000': '#000000', 'bgcolor-001': '#800000', 'bgcolor-002': '#008000', - 'bgcolor-003': '#808000', 'bgcolor-004': '#000080', 'bgcolor-005': '#800080', - 'bgcolor-006': '#008080', 'bgcolor-007': '#c0c0c0', 'bgcolor-008': '#808080', - 'bgcolor-009': '#ff0000', 'bgcolor-010': '#00ff00', 'bgcolor-011': '#ffff00', - 'bgcolor-012': '#0000ff', 'bgcolor-013': '#ff00ff', 'bgcolor-014': '#00ffff', - 'bgcolor-015': '#ffffff', 'bgcolor-016': '#000000', 'bgcolor-017': '#00005f', - 'bgcolor-018': '#000087', 'bgcolor-019': '#0000af', 'bgcolor-020': '#0000df', - 'bgcolor-021': '#0000ff', 'bgcolor-022': '#005f00', 'bgcolor-023': '#005f5f', - 'bgcolor-024': '#005f87', 'bgcolor-025': '#005faf', 'bgcolor-026': '#005fdf', - 'bgcolor-027': '#005fff', 'bgcolor-028': '#008700', 'bgcolor-029': '#00875f', - 'bgcolor-030': '#008787', 'bgcolor-031': '#0087af', 'bgcolor-032': '#0087df', - 'bgcolor-033': '#0087ff', 'bgcolor-034': '#00af00', 'bgcolor-035': '#00af5f', - 'bgcolor-036': '#00af87', 'bgcolor-037': '#00afaf', 'bgcolor-038': '#00afdf', - 'bgcolor-039': '#00afff', 'bgcolor-040': '#00df00', 'bgcolor-041': '#00df5f', - 'bgcolor-042': '#00df87', 'bgcolor-043': '#00dfaf', 'bgcolor-044': '#00dfdf', - 'bgcolor-045': '#00dfff', 'bgcolor-046': '#00ff00', 'bgcolor-047': '#00ff5f', - 'bgcolor-048': '#00ff87', 'bgcolor-049': '#00ffaf', 'bgcolor-050': '#00ffdf', - 'bgcolor-051': '#00ffff', 'bgcolor-052': '#5f0000', 'bgcolor-053': '#5f005f', - 'bgcolor-054': '#5f0087', 'bgcolor-055': '#5f00af', 'bgcolor-056': '#5f00df', - 'bgcolor-057': '#5f00ff', 'bgcolor-058': '#5f5f00', 'bgcolor-059': '#5f5f5f', - 'bgcolor-060': '#5f5f87', 'bgcolor-061': '#5f5faf', 'bgcolor-062': '#5f5fdf', - 'bgcolor-063': '#5f5fff', 'bgcolor-064': '#5f8700', 'bgcolor-065': '#5f875f', - 'bgcolor-066': '#5f8787', 'bgcolor-067': '#5f87af', 'bgcolor-068': '#5f87df', - 'bgcolor-069': '#5f87ff', 'bgcolor-070': '#5faf00', 'bgcolor-071': '#5faf5f', - 'bgcolor-072': '#5faf87', 'bgcolor-073': '#5fafaf', 'bgcolor-074': '#5fafdf', - 'bgcolor-075': '#5fafff', 'bgcolor-076': '#5fdf00', 'bgcolor-077': '#5fdf5f', - 'bgcolor-078': '#5fdf87', 'bgcolor-079': '#5fdfaf', 'bgcolor-080': '#5fdfdf', - 'bgcolor-081': '#5fdfff', 'bgcolor-082': '#5fff00', 'bgcolor-083': '#5fff5f', - 'bgcolor-084': '#5fff87', 'bgcolor-085': '#5fffaf', 'bgcolor-086': '#5fffdf', - 'bgcolor-087': '#5fffff', 'bgcolor-088': '#870000', 'bgcolor-089': '#87005f', - 'bgcolor-090': '#870087', 'bgcolor-091': '#8700af', 'bgcolor-092': '#8700df', - 'bgcolor-093': '#8700ff', 'bgcolor-094': '#875f00', 'bgcolor-095': '#875f5f', - 'bgcolor-096': '#875f87', 'bgcolor-097': '#875faf', 'bgcolor-098': '#875fdf', - 'bgcolor-099': '#875fff', 'bgcolor-100': '#878700', 'bgcolor-101': '#87875f', - 'bgcolor-102': '#878787', 'bgcolor-103': '#8787af', 'bgcolor-104': '#8787df', - 'bgcolor-105': '#8787ff', 'bgcolor-106': '#87af00', 'bgcolor-107': '#87af5f', - 'bgcolor-108': '#87af87', 'bgcolor-109': '#87afaf', 'bgcolor-110': '#87afdf', - 'bgcolor-111': '#87afff', 'bgcolor-112': '#87df00', 'bgcolor-113': '#87df5f', - 'bgcolor-114': '#87df87', 'bgcolor-115': '#87dfaf', 'bgcolor-116': '#87dfdf', - 'bgcolor-117': '#87dfff', 'bgcolor-118': '#87ff00', 'bgcolor-119': '#87ff5f', - 'bgcolor-120': '#87ff87', 'bgcolor-121': '#87ffaf', 'bgcolor-122': '#87ffdf', - 'bgcolor-123': '#87ffff', 'bgcolor-124': '#af0000', 'bgcolor-125': '#af005f', - 'bgcolor-126': '#af0087', 'bgcolor-127': '#af00af', 'bgcolor-128': '#af00df', - 'bgcolor-129': '#af00ff', 'bgcolor-130': '#af5f00', 'bgcolor-131': '#af5f5f', - 'bgcolor-132': '#af5f87', 'bgcolor-133': '#af5faf', 'bgcolor-134': '#af5fdf', - 'bgcolor-135': '#af5fff', 'bgcolor-136': '#af8700', 'bgcolor-137': '#af875f', - 'bgcolor-138': '#af8787', 'bgcolor-139': '#af87af', 'bgcolor-140': '#af87df', - 'bgcolor-141': '#af87ff', 'bgcolor-142': '#afaf00', 'bgcolor-143': '#afaf5f', - 'bgcolor-144': '#afaf87', 'bgcolor-145': '#afafaf', 'bgcolor-146': '#afafdf', - 'bgcolor-147': '#afafff', 'bgcolor-148': '#afdf00', 'bgcolor-149': '#afdf5f', - 'bgcolor-150': '#afdf87', 'bgcolor-151': '#afdfaf', 'bgcolor-152': '#afdfdf', - 'bgcolor-153': '#afdfff', 'bgcolor-154': '#afff00', 'bgcolor-155': '#afff5f', - 'bgcolor-156': '#afff87', 'bgcolor-157': '#afffaf', 'bgcolor-158': '#afffdf', - 'bgcolor-159': '#afffff', 'bgcolor-160': '#df0000', 'bgcolor-161': '#df005f', - 'bgcolor-162': '#df0087', 'bgcolor-163': '#df00af', 'bgcolor-164': '#df00df', - 'bgcolor-165': '#df00ff', 'bgcolor-166': '#df5f00', 'bgcolor-167': '#df5f5f', - 'bgcolor-168': '#df5f87', 'bgcolor-169': '#df5faf', 'bgcolor-170': '#df5fdf', - 'bgcolor-171': '#df5fff', 'bgcolor-172': '#df8700', 'bgcolor-173': '#df875f', - 'bgcolor-174': '#df8787', 'bgcolor-175': '#df87af', 'bgcolor-176': '#df87df', - 'bgcolor-177': '#df87ff', 'bgcolor-178': '#dfaf00', 'bgcolor-179': '#dfaf5f', - 'bgcolor-180': '#dfaf87', 'bgcolor-181': '#dfafaf', 'bgcolor-182': '#dfafdf', - 'bgcolor-183': '#dfafff', 'bgcolor-184': '#dfdf00', 'bgcolor-185': '#dfdf5f', - 'bgcolor-186': '#dfdf87', 'bgcolor-187': '#dfdfaf', 'bgcolor-188': '#dfdfdf', - 'bgcolor-189': '#dfdfff', 'bgcolor-190': '#dfff00', 'bgcolor-191': '#dfff5f', - 'bgcolor-192': '#dfff87', 'bgcolor-193': '#dfffaf', 'bgcolor-194': '#dfffdf', - 'bgcolor-195': '#dfffff', 'bgcolor-196': '#ff0000', 'bgcolor-197': '#ff005f', - 'bgcolor-198': '#ff0087', 'bgcolor-199': '#ff00af', 'bgcolor-200': '#ff00df', - 'bgcolor-201': '#ff00ff', 'bgcolor-202': '#ff5f00', 'bgcolor-203': '#ff5f5f', - 'bgcolor-204': '#ff5f87', 'bgcolor-205': '#ff5faf', 'bgcolor-206': '#ff5fdf', - 'bgcolor-207': '#ff5fff', 'bgcolor-208': '#ff8700', 'bgcolor-209': '#ff875f', - 'bgcolor-210': '#ff8787', 'bgcolor-211': '#ff87af', 'bgcolor-212': '#ff87df', - 'bgcolor-213': '#ff87ff', 'bgcolor-214': '#ffaf00', 'bgcolor-215': '#ffaf5f', - 'bgcolor-216': '#ffaf87', 'bgcolor-217': '#ffafaf', 'bgcolor-218': '#ffafdf', - 'bgcolor-219': '#ffafff', 'bgcolor-220': '#ffdf00', 'bgcolor-221': '#ffdf5f', - 'bgcolor-222': '#ffdf87', 'bgcolor-223': '#ffdfaf', 'bgcolor-224': '#ffdfdf', - 'bgcolor-225': '#ffdfff', 'bgcolor-226': '#ffff00', 'bgcolor-227': '#ffff5f', - 'bgcolor-228': '#ffff87', 'bgcolor-229': '#ffffaf', 'bgcolor-230': '#ffffdf', - 'bgcolor-231': '#ffffff', 'bgcolor-232': '#080808', 'bgcolor-233': '#121212', - 'bgcolor-234': '#1c1c1c', 'bgcolor-235': '#262626', 'bgcolor-236': '#303030', - 'bgcolor-237': '#3a3a3a', 'bgcolor-238': '#444444', 'bgcolor-239': '#4e4e4e', - 'bgcolor-240': '#585858', 'bgcolor-241': '#606060', 'bgcolor-242': '#666666', - 'bgcolor-243': '#767676', 'bgcolor-244': '#808080', 'bgcolor-245': '#8a8a8a', - 'bgcolor-246': '#949494', 'bgcolor-247': '#9e9e9e', 'bgcolor-248': '#a8a8a8', - 'bgcolor-249': '#b2b2b2', 'bgcolor-250': '#bcbcbc', 'bgcolor-251': '#c6c6c6', - 'bgcolor-252': '#d0d0d0', 'bgcolor-253': '#dadada', 'bgcolor-254': '#e4e4e4', - 'bgcolor-255': '#eeeeee'} +COLOR_INDICE_TO_HEX = { + 'color-000': '#000000', 'color-001': '#800000', 'color-002': '#008000', 'color-003': '#808000', + 'color-004': '#000080', 'color-005': '#800080', 'color-006': '#008080', 'color-007': '#c0c0c0', + 'color-008': '#808080', 'color-009': '#ff0000', 'color-010': '#00ff00', 'color-011': '#ffff00', + 'color-012': '#0000ff', 'color-013': '#ff00ff', 'color-014': '#00ffff', 'color-015': '#ffffff', + 'color-016': '#000000', 'color-017': '#00005f', 'color-018': '#000087', 'color-019': '#0000af', + 'color-020': '#0000df', 'color-021': '#0000ff', 'color-022': '#005f00', 'color-023': '#005f5f', + 'color-024': '#005f87', 'color-025': '#005faf', 'color-026': '#005fdf', 'color-027': '#005fff', + 'color-028': '#008700', 'color-029': '#00875f', 'color-030': '#008787', 'color-031': '#0087af', + 'color-032': '#0087df', 'color-033': '#0087ff', 'color-034': '#00af00', 'color-035': '#00af5f', + 'color-036': '#00af87', 'color-037': '#00afaf', 'color-038': '#00afdf', 'color-039': '#00afff', + 'color-040': '#00df00', 'color-041': '#00df5f', 'color-042': '#00df87', 'color-043': '#00dfaf', + 'color-044': '#00dfdf', 'color-045': '#00dfff', 'color-046': '#00ff00', 'color-047': '#00ff5f', + 'color-048': '#00ff87', 'color-049': '#00ffaf', 'color-050': '#00ffdf', 'color-051': '#00ffff', + 'color-052': '#5f0000', 'color-053': '#5f005f', 'color-054': '#5f0087', 'color-055': '#5f00af', + 'color-056': '#5f00df', 'color-057': '#5f00ff', 'color-058': '#5f5f00', 'color-059': '#5f5f5f', + 'color-060': '#5f5f87', 'color-061': '#5f5faf', 'color-062': '#5f5fdf', 'color-063': '#5f5fff', + 'color-064': '#5f8700', 'color-065': '#5f875f', 'color-066': '#5f8787', 'color-067': '#5f87af', + 'color-068': '#5f87df', 'color-069': '#5f87ff', 'color-070': '#5faf00', 'color-071': '#5faf5f', + 'color-072': '#5faf87', 'color-073': '#5fafaf', 'color-074': '#5fafdf', 'color-075': '#5fafff', + 'color-076': '#5fdf00', 'color-077': '#5fdf5f', 'color-078': '#5fdf87', 'color-079': '#5fdfaf', + 'color-080': '#5fdfdf', 'color-081': '#5fdfff', 'color-082': '#5fff00', 'color-083': '#5fff5f', + 'color-084': '#5fff87', 'color-085': '#5fffaf', 'color-086': '#5fffdf', 'color-087': '#5fffff', + 'color-088': '#870000', 'color-089': '#87005f', 'color-090': '#870087', 'color-091': '#8700af', + 'color-092': '#8700df', 'color-093': '#8700ff', 'color-094': '#875f00', 'color-095': '#875f5f', + 'color-096': '#875f87', 'color-097': '#875faf', 'color-098': '#875fdf', 'color-099': '#875fff', + 'color-100': '#878700', 'color-101': '#87875f', 'color-102': '#878787', 'color-103': '#8787af', + 'color-104': '#8787df', 'color-105': '#8787ff', 'color-106': '#87af00', 'color-107': '#87af5f', + 'color-108': '#87af87', 'color-109': '#87afaf', 'color-110': '#87afdf', 'color-111': '#87afff', + 'color-112': '#87df00', 'color-113': '#87df5f', 'color-114': '#87df87', 'color-115': '#87dfaf', + 'color-116': '#87dfdf', 'color-117': '#87dfff', 'color-118': '#87ff00', 'color-119': '#87ff5f', + 'color-120': '#87ff87', 'color-121': '#87ffaf', 'color-122': '#87ffdf', 'color-123': '#87ffff', + 'color-124': '#af0000', 'color-125': '#af005f', 'color-126': '#af0087', 'color-127': '#af00af', + 'color-128': '#af00df', 'color-129': '#af00ff', 'color-130': '#af5f00', 'color-131': '#af5f5f', + 'color-132': '#af5f87', 'color-133': '#af5faf', 'color-134': '#af5fdf', 'color-135': '#af5fff', + 'color-136': '#af8700', 'color-137': '#af875f', 'color-138': '#af8787', 'color-139': '#af87af', + 'color-140': '#af87df', 'color-141': '#af87ff', 'color-142': '#afaf00', 'color-143': '#afaf5f', + 'color-144': '#afaf87', 'color-145': '#afafaf', 'color-146': '#afafdf', 'color-147': '#afafff', + 'color-148': '#afdf00', 'color-149': '#afdf5f', 'color-150': '#afdf87', 'color-151': '#afdfaf', + 'color-152': '#afdfdf', 'color-153': '#afdfff', 'color-154': '#afff00', 'color-155': '#afff5f', + 'color-156': '#afff87', 'color-157': '#afffaf', 'color-158': '#afffdf', 'color-159': '#afffff', + 'color-160': '#df0000', 'color-161': '#df005f', 'color-162': '#df0087', 'color-163': '#df00af', + 'color-164': '#df00df', 'color-165': '#df00ff', 'color-166': '#df5f00', 'color-167': '#df5f5f', + 'color-168': '#df5f87', 'color-169': '#df5faf', 'color-170': '#df5fdf', 'color-171': '#df5fff', + 'color-172': '#df8700', 'color-173': '#df875f', 'color-174': '#df8787', 'color-175': '#df87af', + 'color-176': '#df87df', 'color-177': '#df87ff', 'color-178': '#dfaf00', 'color-179': '#dfaf5f', + 'color-180': '#dfaf87', 'color-181': '#dfafaf', 'color-182': '#dfafdf', 'color-183': '#dfafff', + 'color-184': '#dfdf00', 'color-185': '#dfdf5f', 'color-186': '#dfdf87', 'color-187': '#dfdfaf', + 'color-188': '#dfdfdf', 'color-189': '#dfdfff', 'color-190': '#dfff00', 'color-191': '#dfff5f', + 'color-192': '#dfff87', 'color-193': '#dfffaf', 'color-194': '#dfffdf', 'color-195': '#dfffff', + 'color-196': '#ff0000', 'color-197': '#ff005f', 'color-198': '#ff0087', 'color-199': '#ff00af', + 'color-200': '#ff00df', 'color-201': '#ff00ff', 'color-202': '#ff5f00', 'color-203': '#ff5f5f', + 'color-204': '#ff5f87', 'color-205': '#ff5faf', 'color-206': '#ff5fdf', 'color-207': '#ff5fff', + 'color-208': '#ff8700', 'color-209': '#ff875f', 'color-210': '#ff8787', 'color-211': '#ff87af', + 'color-212': '#ff87df', 'color-213': '#ff87ff', 'color-214': '#ffaf00', 'color-215': '#ffaf5f', + 'color-216': '#ffaf87', 'color-217': '#ffafaf', 'color-218': '#ffafdf', 'color-219': '#ffafff', + 'color-220': '#ffdf00', 'color-221': '#ffdf5f', 'color-222': '#ffdf87', 'color-223': '#ffdfaf', + 'color-224': '#ffdfdf', 'color-225': '#ffdfff', 'color-226': '#ffff00', 'color-227': '#ffff5f', + 'color-228': '#ffff87', 'color-229': '#ffffaf', 'color-230': '#ffffdf', 'color-231': '#ffffff', + 'color-232': '#080808', 'color-233': '#121212', 'color-234': '#1c1c1c', 'color-235': '#262626', + 'color-236': '#303030', 'color-237': '#3a3a3a', 'color-238': '#444444', 'color-239': '#4e4e4e', + 'color-240': '#585858', 'color-241': '#606060', 'color-242': '#666666', 'color-243': '#767676', + 'color-244': '#808080', 'color-245': '#8a8a8a', 'color-246': '#949494', 'color-247': '#9e9e9e', + 'color-248': '#a8a8a8', 'color-249': '#b2b2b2', 'color-250': '#bcbcbc', 'color-251': '#c6c6c6', + 'color-252': '#d0d0d0', 'color-253': '#dadada', 'color-254': '#e4e4e4', 'color-255': '#eeeeee', + 'bgcolor-000': '#000000', 'bgcolor-001': '#800000', 'bgcolor-002': '#008000', + 'bgcolor-003': '#808000', 'bgcolor-004': '#000080', 'bgcolor-005': '#800080', + 'bgcolor-006': '#008080', 'bgcolor-007': '#c0c0c0', 'bgcolor-008': '#808080', + 'bgcolor-009': '#ff0000', 'bgcolor-010': '#00ff00', 'bgcolor-011': '#ffff00', + 'bgcolor-012': '#0000ff', 'bgcolor-013': '#ff00ff', 'bgcolor-014': '#00ffff', + 'bgcolor-015': '#ffffff', 'bgcolor-016': '#000000', 'bgcolor-017': '#00005f', + 'bgcolor-018': '#000087', 'bgcolor-019': '#0000af', 'bgcolor-020': '#0000df', + 'bgcolor-021': '#0000ff', 'bgcolor-022': '#005f00', 'bgcolor-023': '#005f5f', + 'bgcolor-024': '#005f87', 'bgcolor-025': '#005faf', 'bgcolor-026': '#005fdf', + 'bgcolor-027': '#005fff', 'bgcolor-028': '#008700', 'bgcolor-029': '#00875f', + 'bgcolor-030': '#008787', 'bgcolor-031': '#0087af', 'bgcolor-032': '#0087df', + 'bgcolor-033': '#0087ff', 'bgcolor-034': '#00af00', 'bgcolor-035': '#00af5f', + 'bgcolor-036': '#00af87', 'bgcolor-037': '#00afaf', 'bgcolor-038': '#00afdf', + 'bgcolor-039': '#00afff', 'bgcolor-040': '#00df00', 'bgcolor-041': '#00df5f', + 'bgcolor-042': '#00df87', 'bgcolor-043': '#00dfaf', 'bgcolor-044': '#00dfdf', + 'bgcolor-045': '#00dfff', 'bgcolor-046': '#00ff00', 'bgcolor-047': '#00ff5f', + 'bgcolor-048': '#00ff87', 'bgcolor-049': '#00ffaf', 'bgcolor-050': '#00ffdf', + 'bgcolor-051': '#00ffff', 'bgcolor-052': '#5f0000', 'bgcolor-053': '#5f005f', + 'bgcolor-054': '#5f0087', 'bgcolor-055': '#5f00af', 'bgcolor-056': '#5f00df', + 'bgcolor-057': '#5f00ff', 'bgcolor-058': '#5f5f00', 'bgcolor-059': '#5f5f5f', + 'bgcolor-060': '#5f5f87', 'bgcolor-061': '#5f5faf', 'bgcolor-062': '#5f5fdf', + 'bgcolor-063': '#5f5fff', 'bgcolor-064': '#5f8700', 'bgcolor-065': '#5f875f', + 'bgcolor-066': '#5f8787', 'bgcolor-067': '#5f87af', 'bgcolor-068': '#5f87df', + 'bgcolor-069': '#5f87ff', 'bgcolor-070': '#5faf00', 'bgcolor-071': '#5faf5f', + 'bgcolor-072': '#5faf87', 'bgcolor-073': '#5fafaf', 'bgcolor-074': '#5fafdf', + 'bgcolor-075': '#5fafff', 'bgcolor-076': '#5fdf00', 'bgcolor-077': '#5fdf5f', + 'bgcolor-078': '#5fdf87', 'bgcolor-079': '#5fdfaf', 'bgcolor-080': '#5fdfdf', + 'bgcolor-081': '#5fdfff', 'bgcolor-082': '#5fff00', 'bgcolor-083': '#5fff5f', + 'bgcolor-084': '#5fff87', 'bgcolor-085': '#5fffaf', 'bgcolor-086': '#5fffdf', + 'bgcolor-087': '#5fffff', 'bgcolor-088': '#870000', 'bgcolor-089': '#87005f', + 'bgcolor-090': '#870087', 'bgcolor-091': '#8700af', 'bgcolor-092': '#8700df', + 'bgcolor-093': '#8700ff', 'bgcolor-094': '#875f00', 'bgcolor-095': '#875f5f', + 'bgcolor-096': '#875f87', 'bgcolor-097': '#875faf', 'bgcolor-098': '#875fdf', + 'bgcolor-099': '#875fff', 'bgcolor-100': '#878700', 'bgcolor-101': '#87875f', + 'bgcolor-102': '#878787', 'bgcolor-103': '#8787af', 'bgcolor-104': '#8787df', + 'bgcolor-105': '#8787ff', 'bgcolor-106': '#87af00', 'bgcolor-107': '#87af5f', + 'bgcolor-108': '#87af87', 'bgcolor-109': '#87afaf', 'bgcolor-110': '#87afdf', + 'bgcolor-111': '#87afff', 'bgcolor-112': '#87df00', 'bgcolor-113': '#87df5f', + 'bgcolor-114': '#87df87', 'bgcolor-115': '#87dfaf', 'bgcolor-116': '#87dfdf', + 'bgcolor-117': '#87dfff', 'bgcolor-118': '#87ff00', 'bgcolor-119': '#87ff5f', + 'bgcolor-120': '#87ff87', 'bgcolor-121': '#87ffaf', 'bgcolor-122': '#87ffdf', + 'bgcolor-123': '#87ffff', 'bgcolor-124': '#af0000', 'bgcolor-125': '#af005f', + 'bgcolor-126': '#af0087', 'bgcolor-127': '#af00af', 'bgcolor-128': '#af00df', + 'bgcolor-129': '#af00ff', 'bgcolor-130': '#af5f00', 'bgcolor-131': '#af5f5f', + 'bgcolor-132': '#af5f87', 'bgcolor-133': '#af5faf', 'bgcolor-134': '#af5fdf', + 'bgcolor-135': '#af5fff', 'bgcolor-136': '#af8700', 'bgcolor-137': '#af875f', + 'bgcolor-138': '#af8787', 'bgcolor-139': '#af87af', 'bgcolor-140': '#af87df', + 'bgcolor-141': '#af87ff', 'bgcolor-142': '#afaf00', 'bgcolor-143': '#afaf5f', + 'bgcolor-144': '#afaf87', 'bgcolor-145': '#afafaf', 'bgcolor-146': '#afafdf', + 'bgcolor-147': '#afafff', 'bgcolor-148': '#afdf00', 'bgcolor-149': '#afdf5f', + 'bgcolor-150': '#afdf87', 'bgcolor-151': '#afdfaf', 'bgcolor-152': '#afdfdf', + 'bgcolor-153': '#afdfff', 'bgcolor-154': '#afff00', 'bgcolor-155': '#afff5f', + 'bgcolor-156': '#afff87', 'bgcolor-157': '#afffaf', 'bgcolor-158': '#afffdf', + 'bgcolor-159': '#afffff', 'bgcolor-160': '#df0000', 'bgcolor-161': '#df005f', + 'bgcolor-162': '#df0087', 'bgcolor-163': '#df00af', 'bgcolor-164': '#df00df', + 'bgcolor-165': '#df00ff', 'bgcolor-166': '#df5f00', 'bgcolor-167': '#df5f5f', + 'bgcolor-168': '#df5f87', 'bgcolor-169': '#df5faf', 'bgcolor-170': '#df5fdf', + 'bgcolor-171': '#df5fff', 'bgcolor-172': '#df8700', 'bgcolor-173': '#df875f', + 'bgcolor-174': '#df8787', 'bgcolor-175': '#df87af', 'bgcolor-176': '#df87df', + 'bgcolor-177': '#df87ff', 'bgcolor-178': '#dfaf00', 'bgcolor-179': '#dfaf5f', + 'bgcolor-180': '#dfaf87', 'bgcolor-181': '#dfafaf', 'bgcolor-182': '#dfafdf', + 'bgcolor-183': '#dfafff', 'bgcolor-184': '#dfdf00', 'bgcolor-185': '#dfdf5f', + 'bgcolor-186': '#dfdf87', 'bgcolor-187': '#dfdfaf', 'bgcolor-188': '#dfdfdf', + 'bgcolor-189': '#dfdfff', 'bgcolor-190': '#dfff00', 'bgcolor-191': '#dfff5f', + 'bgcolor-192': '#dfff87', 'bgcolor-193': '#dfffaf', 'bgcolor-194': '#dfffdf', + 'bgcolor-195': '#dfffff', 'bgcolor-196': '#ff0000', 'bgcolor-197': '#ff005f', + 'bgcolor-198': '#ff0087', 'bgcolor-199': '#ff00af', 'bgcolor-200': '#ff00df', + 'bgcolor-201': '#ff00ff', 'bgcolor-202': '#ff5f00', 'bgcolor-203': '#ff5f5f', + 'bgcolor-204': '#ff5f87', 'bgcolor-205': '#ff5faf', 'bgcolor-206': '#ff5fdf', + 'bgcolor-207': '#ff5fff', 'bgcolor-208': '#ff8700', 'bgcolor-209': '#ff875f', + 'bgcolor-210': '#ff8787', 'bgcolor-211': '#ff87af', 'bgcolor-212': '#ff87df', + 'bgcolor-213': '#ff87ff', 'bgcolor-214': '#ffaf00', 'bgcolor-215': '#ffaf5f', + 'bgcolor-216': '#ffaf87', 'bgcolor-217': '#ffafaf', 'bgcolor-218': '#ffafdf', + 'bgcolor-219': '#ffafff', 'bgcolor-220': '#ffdf00', 'bgcolor-221': '#ffdf5f', + 'bgcolor-222': '#ffdf87', 'bgcolor-223': '#ffdfaf', 'bgcolor-224': '#ffdfdf', + 'bgcolor-225': '#ffdfff', 'bgcolor-226': '#ffff00', 'bgcolor-227': '#ffff5f', + 'bgcolor-228': '#ffff87', 'bgcolor-229': '#ffffaf', 'bgcolor-230': '#ffffdf', + 'bgcolor-231': '#ffffff', 'bgcolor-232': '#080808', 'bgcolor-233': '#121212', + 'bgcolor-234': '#1c1c1c', 'bgcolor-235': '#262626', 'bgcolor-236': '#303030', + 'bgcolor-237': '#3a3a3a', 'bgcolor-238': '#444444', 'bgcolor-239': '#4e4e4e', + 'bgcolor-240': '#585858', 'bgcolor-241': '#606060', 'bgcolor-242': '#666666', + 'bgcolor-243': '#767676', 'bgcolor-244': '#808080', 'bgcolor-245': '#8a8a8a', + 'bgcolor-246': '#949494', 'bgcolor-247': '#9e9e9e', 'bgcolor-248': '#a8a8a8', + 'bgcolor-249': '#b2b2b2', 'bgcolor-250': '#bcbcbc', 'bgcolor-251': '#c6c6c6', + 'bgcolor-252': '#d0d0d0', 'bgcolor-253': '#dadada', 'bgcolor-254': '#e4e4e4', + 'bgcolor-255': '#eeeeee' +} + + +""" +The classes below exist to properly encapsulate text and other tag classes +because the order of how tags are opened and closed are important to display in godot. +""" class RootTag(object): @@ -256,7 +258,8 @@ class UrlTag(BBCodeTag): class TextToBBCODEparser(TextToHTMLparser): """ - This class describes a parser for converting from ANSI to html. + This class describes a parser for converting from ANSI to BBCode. + It inherits from the TextToHTMLParser and overrides the specifics for bbcode. """ def convert_urls(self, text): @@ -266,7 +269,7 @@ class TextToBBCODEparser(TextToHTMLparser): def sub_mxp_links(self, match): """ Helper method to be passed to re.sub, - replaces MXP links with HTML code. + replaces MXP links with bbcode. Args: match (re.Matchobject): Match for substitution. @@ -283,16 +286,16 @@ class TextToBBCODEparser(TextToHTMLparser): def sub_mxp_urls(self, match): """ Helper method to be passed to re.sub, - replaces MXP links with HTML code. + replaces MXP links with bbcode. Args: match (re.Matchobject): Match for substitution. Returns: text (str): Processed text. """ + url, text = [grp.replace('"', "\\"") for grp in match.groups()] - val = r"""{text}""".format( - url=url, text=text - ) + val = f"[url={url}]{text}[/url]" + return val def sub_text(self, match): @@ -519,8 +522,6 @@ class TextToBBCODEparser(TextToHTMLparser): result = self.format_styles(result) result = self.remove_backspaces(result) result = self.convert_urls(result) - # clean out eventual ansi that was missed - ## result = parse_ansi(result, strip_ansi=True) return result diff --git a/evennia/contrib/base_systems/godotwebsocket/webclient.py b/evennia/contrib/base_systems/godotwebsocket/webclient.py index a6be1887f3..7923bb0040 100644 --- a/evennia/contrib/base_systems/godotwebsocket/webclient.py +++ b/evennia/contrib/base_systems/godotwebsocket/webclient.py @@ -1,3 +1,8 @@ +""" +Godot Websocket - ChrisLR 2022 + +This file contains the code necessary to dedicate a port to communicate with Godot via Websockets. +""" import json from autobahn.twisted import WebSocketServerFactory @@ -11,6 +16,11 @@ from evennia.settings_default import LOCKDOWN_MODE class GodotWebSocketClient(webclient.WebSocketClient): + """ + Implements the server-side of the Websocket connection specific to Godot. + It inherits from the basic Websocket implementation and changes only what is necessary. + + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.protocol_key = "godotclient/websocket" @@ -18,17 +28,15 @@ class GodotWebSocketClient(webclient.WebSocketClient): def send_text(self, *args, **kwargs): """ Send text data. This will pre-process the text for - color-replacement, conversion to html etc. + color-replacement, conversion to bbcode etc. Args: text (str): Text to send. Keyword Args: options (dict): Options-dict with the following keys understood: - - raw (bool): No parsing at all (leave ansi-to-html markers unparsed). - nocolor (bool): Clean out all color. - - screenreader (bool): Use Screenreader mode. - - send_prompt (bool): Send a prompt with parsed html + - send_prompt (bool): Send a prompt with parsed bbcode """ if args: @@ -42,24 +50,11 @@ class GodotWebSocketClient(webclient.WebSocketClient): flags = self.protocol_flags options = kwargs.pop("options", {}) - raw = options.get("raw", flags.get("RAW", False)) - client_raw = options.get("client_raw", False) nocolor = options.get("nocolor", flags.get("NOCOLOR", False)) - screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) prompt = options.get("send_prompt", False) - if screenreader: - # screenreader mode cleans up output - text = webclient.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) - text = webclient._RE_SCREENREADER_REGEX.sub("", text) cmd = "prompt" if prompt else "text" - if raw: - if client_raw: - args[0] = text - else: - args[0] = webclient.html.escape(text) # escape html! - else: - args[0] = parse_to_bbcode(text, strip_ansi=nocolor) + args[0] = parse_to_bbcode(text, strip_ansi=nocolor) # send to client on required form [cmdname, args, kwargs] self.sendLine(json.dumps([cmd, args, kwargs])) From f0a2d55c42c89134a525e324810880ce84a1d917 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Fri, 25 Nov 2022 16:04:20 -0500 Subject: [PATCH 04/13] Docs --- .../base_systems/godotwebsocket/README.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 evennia/contrib/base_systems/godotwebsocket/README.md diff --git a/evennia/contrib/base_systems/godotwebsocket/README.md b/evennia/contrib/base_systems/godotwebsocket/README.md new file mode 100644 index 0000000000..6917f445fc --- /dev/null +++ b/evennia/contrib/base_systems/godotwebsocket/README.md @@ -0,0 +1,124 @@ +# Godot Websocket + +_Contrib by ChrisLR 2022_ + + +# Overview + +This contrib allows you to connect a Godot Client directly to your mud, + +and display regular text with color in Godot's RichTextLabel using BBCode. + +You can use Godot to provide advanced functionality with proper Evennia support. + + +# How to make a basic install + +### Evennia side +You need to add the following settings in your settings.py and restart evennia. + +```python +PORTAL_SERVICES_PLUGIN_MODULES.append('evennia.contrib.base_systems.godotwebsocket.webclient') +GODOT_CLIENT_WEBSOCKET_PORT = 4008 +GODOT_CLIENT_WEBSOCKET_CLIENT_INTERFACE = "127.0.0.1" +``` + +This will make evennia listen on the port 4008 for Godot. + + +### Godot side + +This section assumes you have knowledge for using Godot and +will not go into details. + +You can follow the minimal client tutorial at +https://docs.godotengine.org/en/stable/tutorials/networking/websocket.html + + +Then you need to change the websocket_url for your mud, like +``` +# The URL we will connect to +export var websocket_url = "ws://localhost:4008" +``` + +Also remove the protocol from + +``` +var err = _client.connect_to_url(websocket_url, ["lws-mirror-protocol"]) +``` + +to + +``` +var err = _client.connect_to_url(websocket_url) +``` + +This will allow you to connect to your mud. + +After that you need to properly handle the data sent by evennia. + +To do this, you should replace your `_on_data` method. + +You will need to parse the JSON received to properly act on the data. + +Here is an example +``` +var data = _client.get_peer(1).get_packet().get_string_from_utf8() +var json_data = JSON.parse(data).result +if json_data[0] == 'text': + for msg in json_data[1]: + label.append_bbcode(msg) +``` + +The first element is the type, it will be `text` if it is a message + +It can be anything you would provide to the Evennia `msg` function, more on that later. + +The second element will be the data related to the type of message, in this case it is a list of text to display. + +Since it is parsed BBCode, we can add that directly to a RichTextLabel by calling its append_bbcode method. + +# Advanced usage + +If you want anything better than fancy text in Godot, you will have + +to leverage Evennia's OOB to send extra data. + +You can [read more on OOB here](https://www.evennia.com/docs/latest/OOB.html#oob). + +Example + +Evennia +```python +caller.msg(coordinates=(9, 2)) +``` + +Godot +```gdscript +func _on_data(): + ... + + if json_data[0] == 'text': + for msg in json_data[1]: + label.append_bbcode(msg) + elif json_data[0] == 'coordinates': + var coords_data = json_data[2] + player.set_pos(coords_data) + + ... +``` + +A good idea would be to set up Godot Signals you can trigger based on the data + +you receive, so you can manage the code better. + +# Known Issues + +Sending SaverDicts and similar objects straight from Evennia .DB will cause issues, + +cast them to dict() or list() before doing so. + + +Godot 3.x Richtext does not support background colors, it will be supported + +in Godot 4.x but the Websockets implementation changes in that version. \ No newline at end of file From 282024b99442b1b90359a28e4e87d8a9f1c001d0 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Fri, 25 Nov 2022 16:07:16 -0500 Subject: [PATCH 05/13] Fix doc --- evennia/contrib/base_systems/godotwebsocket/text2bbcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py index 664e107ba8..635fb6bd9e 100644 --- a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py @@ -536,6 +536,6 @@ BBCODE_PARSER = TextToBBCODEparser() def parse_to_bbcode(string, strip_ansi=False, parser=BBCODE_PARSER): """ - Parses a string, replace ANSI markup with html + Parses a string, replace ANSI markup with bbcode """ return parser.parse(string, strip_ansi=strip_ansi) From bcbe5cd0ead90f4d0d9d9c8fae92441b5bd32b9d Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Fri, 25 Nov 2022 16:10:12 -0500 Subject: [PATCH 06/13] Fix doc --- evennia/contrib/base_systems/godotwebsocket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/__init__.py b/evennia/contrib/base_systems/godotwebsocket/__init__.py index 0e485605a0..e209670d3e 100644 --- a/evennia/contrib/base_systems/godotwebsocket/__init__.py +++ b/evennia/contrib/base_systems/godotwebsocket/__init__.py @@ -9,4 +9,4 @@ You can simply connect the resulting text to Godot's RichTextLabel and have the You could also pass extra data to this client for advanced functionality. See the docs for more information. -""" \ No newline at end of file +""" From 2cf60bd04d2087f4e7902158abea1c03dada91a2 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Fri, 25 Nov 2022 16:16:03 -0500 Subject: [PATCH 07/13] Fix doc --- evennia/contrib/base_systems/godotwebsocket/text2bbcode.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py index 635fb6bd9e..6ab4a7c3cf 100644 --- a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py @@ -318,8 +318,7 @@ class TextToBBCODEparser(TextToHTMLparser): def format_styles(self, text): """ - Takes a string with parsed ANSI codes and replaces them with - HTML spans and CSS classes. + Takes a string with parsed ANSI codes and replaces them with bbcode style tags Args: text (str): The string to process. From 929591c29ff5b5f19c784c402f60f063b77e9e44 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Sun, 27 Nov 2022 16:47:14 -0500 Subject: [PATCH 08/13] Add imports to package --- evennia/contrib/base_systems/godotwebsocket/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evennia/contrib/base_systems/godotwebsocket/__init__.py b/evennia/contrib/base_systems/godotwebsocket/__init__.py index e209670d3e..c6b9827538 100644 --- a/evennia/contrib/base_systems/godotwebsocket/__init__.py +++ b/evennia/contrib/base_systems/godotwebsocket/__init__.py @@ -10,3 +10,5 @@ You could also pass extra data to this client for advanced functionality. See the docs for more information. """ +from evennia.contrib.base_systems.godotwebsocket.text2bbcode import parse_to_bbcode, BBCODE_PARSER +from evennia.contrib.base_systems.godotwebsocket.webclient import GodotWebSocketClient From b0d0e65b42982bad78dda5a6ab40b1bfc2ebd5d9 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Sun, 27 Nov 2022 16:48:13 -0500 Subject: [PATCH 09/13] Removed explicit inheritance --- evennia/contrib/base_systems/godotwebsocket/text2bbcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py index 6ab4a7c3cf..e6004ce362 100644 --- a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py @@ -171,7 +171,7 @@ because the order of how tags are opened and closed are important to display in """ -class RootTag(object): +class RootTag: __slots__ = ('child',) def __init__(self): @@ -181,7 +181,7 @@ class RootTag(object): return str(self.child) if self.child else "" -class ChildTag(object): +class ChildTag: def __init__(self, parent): self.parent = parent if parent: From bc825941d81fa52ac1075b9b75d30484de710820 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Sun, 27 Nov 2022 16:50:02 -0500 Subject: [PATCH 10/13] Rewrote comment to docstring --- .../contrib/base_systems/godotwebsocket/text2bbcode.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py index e6004ce362..810263e650 100644 --- a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py @@ -263,6 +263,15 @@ class TextToBBCODEparser(TextToHTMLparser): """ def convert_urls(self, text): + """ + Converts urls within text to bbcode style + + Args: + text (str): Text to parse + + Returns: + text (str): Processed text + """ # Converts to bbcode styled urls return self.re_url.sub(r'[url=\1]\1[/url]\2', text) From 5b45e401441d2cb9b5d1653698984e8829722fe0 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Sun, 27 Nov 2022 17:08:41 -0500 Subject: [PATCH 11/13] Added docstring --- .../godotwebsocket/text2bbcode.py | 43 +++++++++++++++++++ .../base_systems/godotwebsocket/webclient.py | 1 + 2 files changed, 44 insertions(+) diff --git a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py index 810263e650..b5a748b30d 100644 --- a/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py +++ b/evennia/contrib/base_systems/godotwebsocket/text2bbcode.py @@ -172,6 +172,10 @@ because the order of how tags are opened and closed are important to display in class RootTag: + """ + The Root tag class made to contain other tags. + """ + __slots__ = ('child',) def __init__(self): @@ -182,6 +186,10 @@ class RootTag: class ChildTag: + """ + A node made to be contained. + """ + def __init__(self, parent): self.parent = parent if parent: @@ -194,6 +202,11 @@ class ChildTag: class TextTag(ChildTag): + """ + A BBCodeTag node to output regular text. + Output: SomeText + """ + __slots__ = ('parent', 'child', 'text') def __init__(self, parent, text): @@ -206,6 +219,10 @@ class TextTag(ChildTag): class BBCodeTag(ChildTag): + """ + Base BBCodeTag node to encapsulate and be encapsulated. + """ + __slots__ = ('parent', 'child',) code = '' @@ -219,14 +236,29 @@ class BBCodeTag(ChildTag): class UnderlineTag(BBCodeTag): + """ + A BBCodeTag node for underlined text. + Output: [u]Underlined Text[/u] + """ + code = 'u' class BlinkTag(BBCodeTag): + """ + A BBCodeTag node for blinking text. + Output: [blink]Blinking Text[/blink] + """ + code = 'blink' class ColorTag(BBCodeTag): + """ + A BBCodeTag node for foreground color. + Output: [fgcolor=#000000]Colorized Text[/fgcolor] + """ + __slots__ = ('parent', 'child', 'color_hex',) code = 'color' @@ -240,10 +272,21 @@ class ColorTag(BBCodeTag): class BGColorTag(ColorTag): + """ + A BBCodeTag node for background color. + Output: [bgcolor=#000000]Colorized Text[/bgcolor] + """ + code = 'bgcolor' class UrlTag(BBCodeTag): + """ + A BBCodeTag node used for urls. + Output: [url=www.example.com]Child Text[/url] + + """ + __slots__ = ('parent', 'child', 'url_data',) code = 'url' diff --git a/evennia/contrib/base_systems/godotwebsocket/webclient.py b/evennia/contrib/base_systems/godotwebsocket/webclient.py index 7923bb0040..ceb3ee4679 100644 --- a/evennia/contrib/base_systems/godotwebsocket/webclient.py +++ b/evennia/contrib/base_systems/godotwebsocket/webclient.py @@ -2,6 +2,7 @@ Godot Websocket - ChrisLR 2022 This file contains the code necessary to dedicate a port to communicate with Godot via Websockets. +It uses the plugin system and should be plugged via settings as detailed in the readme. """ import json From 50f6182770118a03539397e379e3b7a1af9005c6 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Sun, 27 Nov 2022 18:30:44 -0500 Subject: [PATCH 12/13] More docs adjustment --- .../base_systems/godotwebsocket/README.md | 95 ++++++++++--------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/README.md b/evennia/contrib/base_systems/godotwebsocket/README.md index 6917f445fc..92d7b9c089 100644 --- a/evennia/contrib/base_systems/godotwebsocket/README.md +++ b/evennia/contrib/base_systems/godotwebsocket/README.md @@ -1,20 +1,14 @@ # Godot Websocket -_Contrib by ChrisLR 2022_ - - -# Overview +Contribution by ChrisLR, 2022 This contrib allows you to connect a Godot Client directly to your mud, - and display regular text with color in Godot's RichTextLabel using BBCode. - You can use Godot to provide advanced functionality with proper Evennia support. -# How to make a basic install +## Installation -### Evennia side You need to add the following settings in your settings.py and restart evennia. ```python @@ -24,69 +18,80 @@ GODOT_CLIENT_WEBSOCKET_CLIENT_INTERFACE = "127.0.0.1" ``` This will make evennia listen on the port 4008 for Godot. +You can change the port and interface as you want. -### Godot side +## Usage -This section assumes you have knowledge for using Godot and -will not go into details. +The tl;dr of it is to connect using a Godot Websocket using the port defined above. +It will let you transfer data from Evennia to Godot, allowing you +to get styled text in a RichTextLabel with bbcode enabled or to handle +the extra data given from Evennia as needed. + + +This section assumes you have basic knowledge on how to use Godot. +You can read the following url for more details on Godot Websockets +and to implement a minimal client. -You can follow the minimal client tutorial at https://docs.godotengine.org/en/stable/tutorials/networking/websocket.html -Then you need to change the websocket_url for your mud, like +Then at the top of the file you must change the url to point at your mud. ``` +extends Node + # The URL we will connect to export var websocket_url = "ws://localhost:4008" -``` - -Also remove the protocol from -``` -var err = _client.connect_to_url(websocket_url, ["lws-mirror-protocol"]) ``` -to + +You must also remove the protocol from the `connect_to_url` call made +within the `_ready` function. ``` -var err = _client.connect_to_url(websocket_url) +func _ready(): + # ... + # Change the following line from this + var err = _client.connect_to_url(websocket_url, ["lws-mirror-protocol"]) + # To this + var err = _client.connect_to_url(websocket_url) + # ... ``` This will allow you to connect to your mud. - After that you need to properly handle the data sent by evennia. - To do this, you should replace your `_on_data` method. - You will need to parse the JSON received to properly act on the data. - Here is an example ``` -var data = _client.get_peer(1).get_packet().get_string_from_utf8() -var json_data = JSON.parse(data).result -if json_data[0] == 'text': - for msg in json_data[1]: - label.append_bbcode(msg) +func _on_data(): + # The following two lines will get us the data from Evennia. + var data = _client.get_peer(1).get_packet().get_string_from_utf8() + var json_data = JSON.parse(data).result + # The json_data is an array + + # The first element informs us this is simple text + # so we add it to the RichTextlabel + if json_data[0] == 'text': + for msg in json_data[1]: + label.append_bbcode(msg) + + # Always useful to print the data and see what we got. + print(data) ``` The first element is the type, it will be `text` if it is a message - -It can be anything you would provide to the Evennia `msg` function, more on that later. - +It can be anything you would provide to the Evennia `msg` function. The second element will be the data related to the type of message, in this case it is a list of text to display. - Since it is parsed BBCode, we can add that directly to a RichTextLabel by calling its append_bbcode method. -# Advanced usage - If you want anything better than fancy text in Godot, you will have - to leverage Evennia's OOB to send extra data. You can [read more on OOB here](https://www.evennia.com/docs/latest/OOB.html#oob). -Example +In this example, we send coordinates whenever we message our character. Evennia ```python @@ -97,10 +102,11 @@ Godot ```gdscript func _on_data(): ... - if json_data[0] == 'text': for msg in json_data[1]: label.append_bbcode(msg) + + # Notice the first element is the name of the kwarg we used from evennia. elif json_data[0] == 'coordinates': var coords_data = json_data[2] player.set_pos(coords_data) @@ -109,16 +115,11 @@ func _on_data(): ``` A good idea would be to set up Godot Signals you can trigger based on the data - you receive, so you can manage the code better. -# Known Issues +## Known Issues -Sending SaverDicts and similar objects straight from Evennia .DB will cause issues, +- Sending SaverDicts and similar objects straight from Evennia .DB will cause issues, + cast them to dict() or list() before doing so. -cast them to dict() or list() before doing so. - - -Godot 3.x Richtext does not support background colors, it will be supported - -in Godot 4.x but the Websockets implementation changes in that version. \ No newline at end of file +- Background colors are only supported by Godot 4. \ No newline at end of file From 05b5a78a6da453e9cb218558105902cb0e655086 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Thu, 1 Dec 2022 12:13:38 -0500 Subject: [PATCH 13/13] Add examples --- .../base_systems/godotwebsocket/README.md | 168 +++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/base_systems/godotwebsocket/README.md b/evennia/contrib/base_systems/godotwebsocket/README.md index 92d7b9c089..b3f6abf99d 100644 --- a/evennia/contrib/base_systems/godotwebsocket/README.md +++ b/evennia/contrib/base_systems/godotwebsocket/README.md @@ -35,8 +35,11 @@ and to implement a minimal client. https://docs.godotengine.org/en/stable/tutorials/networking/websocket.html +The rest of this document will be for Godot 3, an example is left at the bottom +of this readme for Godot 4. -Then at the top of the file you must change the url to point at your mud. + +At the top of the file you must change the url to point at your mud. ``` extends Node @@ -122,4 +125,165 @@ you receive, so you can manage the code better. - Sending SaverDicts and similar objects straight from Evennia .DB will cause issues, cast them to dict() or list() before doing so. -- Background colors are only supported by Godot 4. \ No newline at end of file +- Background colors are only supported by Godot 4. + +## Godot 3 Example + +This is an example of a Script to use in Godot 3. +The script can be attached to the root UI node. + +```gdscript +extends Node + +# The URL to connect to, should be your mud. +export var websocket_url = "ws://127.0.0.1:4008" + +# These are references to controls in the scene +onready var parent = get_parent() +onready var label = parent.get_node("%ChatLog") +onready var txtEdit = parent.get_node("%ChatInput") + +onready var room = get_node("/root/World/Room") + +# Our WebSocketClient instance +var _client = WebSocketClient.new() + +var is_connected = false + +func _ready(): + # Connect base signals to get notified of connection open, close, errors and messages + _client.connect("connection_closed", self, "_closed") + _client.connect("connection_error", self, "_closed") + _client.connect("connection_established", self, "_connected") + _client.connect("data_received", self, "_on_data") + print('Ready') + + # Initiate connection to the given URL. + var err = _client.connect_to_url(websocket_url) + if err != OK: + print("Unable to connect") + set_process(false) + +func _closed(was_clean = false): + # was_clean will tell you if the disconnection was correctly notified + # by the remote peer before closing the socket. + print("Closed, clean: ", was_clean) + set_process(false) + +func _connected(proto = ""): + is_connected = true + print("Connected with protocol: ", proto) + +func _on_data(): + # This is called when Godot receives data from evennia + var data = _client.get_peer(1).get_packet().get_string_from_utf8() + var json_data = JSON.parse(data).result + # Here we have the data from Evennia which is an array. + # The first element will be text if it is a message + # and would be the key of the OOB data you passed otherwise. + if json_data[0] == 'text': + # In this case, we simply append the data as bbcode to our label. + for msg in json_data[1]: + label.append_bbcode(msg) + elif json_data[0] == 'coordinates': + # Dummy signal emitted if we wanted to handle the new coordinates + # elsewhere in the project. + self.emit_signal('updated_coordinates', json_data[1]) + + + # We only print this for easier debugging. + print(data) + +func _process(delta): + # Required for websocket to properly react + _client.poll() + +func _on_button_send(): + # This is called when we press the button in the scene + # with a connected signal, it sends the written message to Evennia. + var msg = txtEdit.text + var msg_arr = ['text', [msg], {}] + var msg_str = JSON.print(msg_arr) + _client.get_peer(1).put_packet(msg_str.to_utf8()) + +func _notification(what): + # This is a special method that allows us to notify Evennia we are closing. + if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST: + if is_connected: + var msg_arr = ['text', ['quit'], {}] + var msg_str = JSON.print(msg_arr) + _client.get_peer(1).put_packet(msg_str.to_utf8()) + get_tree().quit() # default behavior + +``` + +## Godot 4 Example + +This is an example of a Script to use in Godot 4. +Note that the version is not final so the code may break. +It requires a WebSocketClientNode as a child of the root node. +The script can be attached to the root UI node. + +```gdscript +extends Control + +# The URL to connect to, should be your mud. +var websocket_url = "ws://127.0.0.1:4008" + +# These are references to controls in the scene +@onready +var label: RichTextLabel = get_node("%ChatLog") +@onready +var txtEdit: TextEdit = get_node("%ChatInput") +@onready +var websocket = get_node("WebSocketClient") + +func _ready(): + # We connect the various signals + websocket.connect('connected_to_server', self._connected) + websocket.connect('connection_closed', self._closed) + websocket.connect('message_received', self._on_data) + + # We attempt to connect and print out the error if we have one. + var result = websocket.connect_to_url(websocket_url) + if result != OK: + print('Could not connect:' + str(result)) + + +func _closed(): + # This emits if the connection was closed by the remote host or unexpectedly + print('Connection closed.') + set_process(false) + +func _connected(): + # This emits when the connection succeeds. + print('Connected!') + +func _on_data(data): + # This is called when Godot receives data from evennia + var json_data = JSON.parse_string(data) + # Here we have the data from Evennia which is an array. + # The first element will be text if it is a message + # and would be the key of the OOB data you passed otherwise. + if json_data[0] == 'text': + # In this case, we simply append the data as bbcode to our label. + for msg in json_data[1]: + # Here we include a newline at every message. + label.append_text("\n" + msg) + elif json_data[0] == 'coordinates': + # Dummy signal emitted if we wanted to handle the new coordinates + # elsewhere in the project. + self.emit_signal('updated_coordinates', json_data[1]) + + # We only print this for easier debugging. + print(data) + +func _on_button_pressed(): + # This is called when we press the button in the scene + # with a connected signal, it sends the written message to Evennia. + var msg = txtEdit.text + var msg_arr = ['text', [msg], {}] + var msg_str = JSON.stringify(msg_arr) + websocket.send(msg_str) + +``` \ No newline at end of file