Source code for nbtparse.minecraft.block_ids

"""Module for looking up blocks, items, entities, etc. by name or number.

Lookups should be performed by whatever :class:`Namespace` object is
appropriate.  The vanilla namespace lives in :obj:`VANILLA`, but some mods add
their own custom blocks.

"""

import abc
import collections
import collections.abc as cabc
import copy
import functools
import importlib
import io
import json
import logging
import numbers
import pathlib
import pkgutil
import sys

from ..semantics import nbtobject
from ..syntax import tags
from .. import exceptions


logger = logging.getLogger(__name__)


[docs]class BlockID: """Information about a block ID.""" def __init__(self, *, name: str=None, block_number: int=None, light: int=0, transparent: bool=False, opacity: int=None, physics: bool=False): self._name = name self._block_number = block_number self._light = light if light is not None else 0 self._transparent = transparent if transparent is not None else False self._opacity = opacity self._physics = physics if physics is not None else False @property def name(self): """The name of this ID, or None if no name was specified. Named objects are renamed when placed into a namespace. """ return self._name
[docs] def rename(self, new_name: str) -> 'BlockID': """Duplicate this named object with a different name.""" if new_name == self.name: return self result = copy.copy(self) result._name = new_name return result
def __repr__(self): return ('BlockID(name={!r}, block_number={!r}, light={!r}, ' 'transparent={!r}, opacity={!r}, physics={!r})' .format(self.name, self.block_number, self.light, self.transparent, self.opacity, self.physics)) @property def block_number(self): """The number used internally when storing this block in terrain.""" return self._block_number @property def light(self): """The amount of light this block emits.""" return self._light @property def transparent(self): """Whether this block is transparent.""" return self._transparent @property def opacity(self): """The amount of light this block impedes. None if :attr:`transparent` is False. """ return self._opacity @property def physics(self): """True if this block falls by itself.""" return self._physics
[docs] def renumber(self, new_id: str) -> 'BlockID': """Duplicate this block ID with a different number""" if new_id == self._block_number: return self result = copy.copy(self) result._block_number = new_id return result
[docs]class Namespace(cabc.Mapping): """A Minecraft namespace. Namespaces associate block names and numbers with each other, and store additional information about blocks, items, and entities. A namespace maps names and numbers to instances of the various FooID classes defined in the ids module. Iterating over a namespace will only produce the names, since every numbered block also has a name. """ def __init__(self, contents: {str: BlockID}, numericals: {int: str}): contents = dict(contents) numericals = dict(numericals) reversed_numericals = {v: k for k, v in numericals.items()} if not contents.keys() >= set(numericals.values()): raise ValueError('numericals.values() should be a subset of ' 'contents.keys()') if len(numericals) != len(reversed_numericals): raise ValueError('numericals.values() must not contain ' 'duplicates.') self._numericals = numericals self._contents = {} for name, identifier in contents.items(): identifier = identifier.rename(name) if name in reversed_numericals: number = reversed_numericals[name] identifier = identifier.renumber(number) self._contents[name] = identifier def __getitem__(self, key): if isinstance(key, numbers.Integral): name = self._numericals[int(key)] else: name = key return self._contents[name] def __iter__(self): return iter(self._contents) def __len__(self): return len(self._contents) def __repr__(self): return '<Namespace (block_ids): {!r} names>'.format(len(self))
def _inflate(deflated: dict) -> BlockID: """Transform JSON output into an Identifier.""" if deflated is None: return None result = BlockID(block_number=deflated.get('id'), transparent=deflated.get('transparent'), light=deflated.get('light'), opacity=deflated.get('opacity'), physics=deflated.get('physics'), ) return result
[docs]def read_config(infile: io.TextIOBase) -> Namespace: """Read a configuration file and produce a namespace dictionary. Return a suitable argument to :func:`register_namespace`. Config file format is simple JSON, encoded in UTF-8:: { "stone": { "id": 1, "item_identifier": { "id": 1, "item_identifier": null, "max_stack": 64, "type": "item" } "light": 0, "opacity": null, "transparent": false, "type": "block" } } Null values may be omitted, except that id and type are mandatory and must not be null. If the above format is not followed or the file is invalid JSON, a :exc:`~.exceptions.ParserError` is raised. """ try: raw_ns = json.load(infile) except ValueError as exc: raise exceptions.ParserError() from exc contents = {name: _inflate(value) for name, value in raw_ns.items()} numericals = {ident.block_number: name for name, ident in contents.items()} return Namespace(contents, numericals)
def _deflate(ident: BlockID) -> dict: result = {} # XXX: Do not change these to if ident.foo; some are integers which # might be zero, or coerce to integers if ident.block_number is not None: result['id'] = ident.block_number if ident.transparent is not None: result['transparent'] = ident.transparent if ident.light is not None: result['light'] = ident.light if ident.opacity is not None: result['opacity'] = ident.opacity return result
[docs]def write_config(outfile: io.TextIOBase, namespace: Namespace, *, pprint=False): """Write a configuration file. Use a format suitable for :func:`read_config`. Nulls are omitted. """ result = {name: _deflate(indent) for name, indent in namespace.items()} if pprint: indent = '\t' separators = (',', ': ') sort_keys = True else: indent = None separators = (',', ':') sort_keys = False json.dump(result, outfile, indent=indent, separators=separators, sort_keys=sort_keys) outfile.write('\n')
VANILLA = None """The namespace containing all vanilla blocks. May be None if the config file cannot be loaded. """ STANDARD_CONFIG_FILENAME = 'standard.json' _config_data = pkgutil.get_data(__name__, STANDARD_CONFIG_FILENAME) if _config_data is not None: _config_data = _config_data.decode('utf8') VANILLA = read_config(io.StringIO(_config_data)) else: logger.error('Vanilla namespace not available') def _lookup_identifier(meta_namespace, key: str) -> BlockID: if type(key) is str: try: namespace, identifier = key.split(':', maxsplit=1) except ValueError: namespace = None identifier = key try: identifier = int(identifier) except ValueError: pass elif type(key) is int: namespace = None identifier = key else: raise KeyError(key) if namespace is None: for namespace in reversed(meta_namespace): contents = meta_namespace[namespace] if identifier in contents: return contents[identifier] else: raise KeyError(key) else: return meta_namespace[namespace][identifier]
[docs]def main(): """Run this module as a script.""" import argparse ids = sys.modules[__name__] # The name of the "standard" namespace, containing ID's used by vanilla # Minecraft STANDARD_NAMESPACE_NAME = 'minecraft' meta_namespace = collections.OrderedDict() meta_namespace[STANDARD_NAMESPACE_NAME] = VANILLA parser = argparse.ArgumentParser(description='Look up a block ID') parser.add_argument('identifier', help='The block number or item ID ' 'to look up, optionally with a namespace prefix ' '(e.g. minecraft:stone)', type=str) parser.add_argument('-N', '--namespace', nargs=2, help='Register an additional namespace before ' 'doing the lookup; may be specified multiple ' 'times', action='append', metavar=('name', 'file')) parser.add_argument('-i', '--force-item', help='Look up an item ID; ' 'convert block IDs to item IDs if necessary.', action='store_true') args = parser.parse_args() if args.namespace is not None: for item in args.namespace: name, file = item contents = read_config(file) meta_namespace[name] = contents try: identifier = _lookup_identifier(meta_namespace, args.identifier) except KeyError: sys.exit('{} is not a valid identifier' .format(args.identifier)) print('{} is block number {}'.format(args.identifier, identifier.block_number)) print('{} is named {}'.format(args.identifier, identifier.name)) if identifier.transparent: print('It is transparent with an opacity of {}' .format(identifier.opacity)) if identifier.light != 0: print('It produces a light level of {}' .format(identifier.light)) if identifier.physics: print('It falls if unsupported')
if __name__ == '__main__': main()