{"id":16374988,"url":"https://github.com/moderocky/centurion","last_synced_at":"2026-03-31T16:30:17.618Z","repository":{"id":97122096,"uuid":"598140432","full_name":"Moderocky/Centurion","owner":"Moderocky","description":"A deployable command framework.","archived":false,"fork":false,"pushed_at":"2023-11-20T11:44:12.000Z","size":310,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-01T04:15:10.413Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Moderocky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-06T13:43:51.000Z","updated_at":"2023-04-10T08:20:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"6c8efaf1-af8c-4156-940d-a870004957f7","html_url":"https://github.com/Moderocky/Centurion","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moderocky%2FCenturion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moderocky%2FCenturion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moderocky%2FCenturion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moderocky%2FCenturion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Moderocky","download_url":"https://codeload.github.com/Moderocky/Centurion/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239954698,"owners_count":19724286,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T03:19:02.275Z","updated_at":"2026-03-31T16:30:17.583Z","avatar_url":"https://github.com/Moderocky.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Centurion\n=====\n\n### Opus #24\n\nA deployable command resolution framework with modules for various domains.\n\n## Maven Information\n\nCenturion is available from `Kenzie`.\n\n```xml\n\n\u003crepository\u003e\n    \u003cid\u003ekenzie\u003c/id\u003e\n    \u003curl\u003ehttps://repo.kenzie.mx/releases\u003c/url\u003e\n\u003c/repository\u003e\n```\n\nIt is split into domain-specific modules. These may contain particular behaviour or handling appropriate to that domain.\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003emx.kenzie\u003c/groupId\u003e\n    \u003cartifactId\u003ecenturion-core\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.1\u003c/version\u003e\n    \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003emx.kenzie\u003c/groupId\u003e\n    \u003cartifactId\u003ecenturion-minecraft\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.1\u003c/version\u003e\n    \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\n## Usage\n\n### Creating a Command\n\nCommands are created in and executed from a `Command` class. \\\nThis has some kind of 'Sender' object that you pass when executing a command, typically to send the feedback to the\ncorrect place. \\\nThe sender could be a system output or log for command-line applications, or a player or user if this is deployed in a\ngame or some kind of interface.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n}\n```\n\n#### Creating Behaviour\n\nThis must override the `create` method, in which you detail the command's behaviour.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\");\n    }\n\n}\n```\n\nCurrently, this code generates an empty, behaviour-less `blob` command. \\\nExecuting this command with `command.execute(user, \"blob\")` will pass but give no feedback.\n\n#### Adding Lapse Behaviour\n\nImagine our `User` sender object has a `.reply(message)` method for sending feedback.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\")\n            .lapse(user -\u003e {\n                user.reply(\"Lapsed!\");\n                return CommandResult.PASSED;\n            });\n    }\n\n}\n```\n\nWe can set up 'lapse' behaviour, which will run by default if nothing else matches. \\\nIf there are no matched argument configurations, or special behaviour found for the input,\nour command will lapse to this function.\n\n### Adding Arguments\n\nMost commands will want some sort of arguments to specify sub-functions.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\")\n            .arg(\"hello\", (user, arguments) -\u003e {\n                user.reply(\"Hello user!\");\n                return CommandResult.PASSED;\n            })\n            .lapse(user -\u003e {\n                user.reply(\"Lapsed!\");\n                return CommandResult.PASSED;\n            });\n    }\n\n}\n```\n\nThis example would create the following command tree:\n\n```\nblob        -\u003e Lapsed!\nblob hello  -\u003e Hello user!\n```\n\nIf our first argument passed to `blob` is exactly `hello` then it will execute our argument function. \\\nIf we pass a different set of arguments (like `foo` or `hello foo`) it will lapse to our default function.\n\n#### Adding Multiple Arguments\n\nCommands can be specified to take multiple sub-arguments, including overloading.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\")\n            .arg(\"hello\", \"there\", (user, arguments) -\u003e {\n                user.reply(\"General Kenobi!\");\n                return CommandResult.PASSED;\n            })\n            .arg(\"hello\", \"world\", (user, arguments) -\u003e {\n                user.reply(\"Hello World!\");\n                return CommandResult.PASSED;\n            })\n            .arg(\"hello\", (user, arguments) -\u003e {\n                user.reply(\"Hello user!\");\n                return CommandResult.PASSED;\n            })\n            .lapse(user -\u003e {\n                user.reply(\"Lapsed!\");\n                return CommandResult.PASSED;\n            });\n    }\n\n}\n```\n\nThis example would create the following command tree:\n\n```\nblob                -\u003e Lapsed!\nblob hello          -\u003e Hello user!\nblob hello there    -\u003e General Kenobi!\nblob hello world    -\u003e Hello World!\n```\n\nThese are called 'literal' arguments, because they match an exact input and nothing else. \\\nThey do not provide anything to the `arguments` parameter of the command function,\nsince we know what the user wrote already.\n\n#### Input Arguments\n\nCommands can also pass an input to a sub-function.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\")\n            .arg(\"hello\", STRING, (user, arguments) -\u003e {\n                user.reply(\"Hello, \" + arguments.get(0));\n                return CommandResult.PASSED;\n            })\n            .arg(\"hello\", \"there\", (user, arguments) -\u003e {\n                user.reply(\"General Kenobi!\");\n                return CommandResult.PASSED;\n            })\n            .arg(\"hello\", (user, arguments) -\u003e {\n                user.reply(\"Hello user!\");\n                return CommandResult.PASSED;\n            })\n            .lapse(user -\u003e {\n                user.reply(\"Lapsed!\");\n                return CommandResult.PASSED;\n            });\n    }\n\n}\n```\n\nThis example would create the following command tree:\n\n```\nblob                -\u003e Lapsed!\nblob hello          -\u003e Hello user!\nblob hello there    -\u003e General Kenobi!\nblob hello \u003cstring\u003e -\u003e Hello, %input%\n```\n\nThese command patterns are automatically weighted according to their input.\nThis means the literal pattern `hello there` will always be checked before the input pattern `hello \u003cstring\u003e`.\n\n#### Other Input Types\n\nRaw text is not the only input that a command can take.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\")\n            .arg(\"hello\", STRING, (user, arguments) -\u003e {\n                user.reply(\"String \" + arguments.get(0));\n                return CommandResult.PASSED;\n            })\n            .arg(\"hello\", BOOLEAN, (user, arguments) -\u003e {\n                user.reply(\"Boolean \" + arguments.get(0));\n                return CommandResult.PASSED;\n            })\n            .arg(\"hello\", INTEGER, (user, arguments) -\u003e {\n                user.reply(\"Number \" + arguments.get(0));\n                return CommandResult.PASSED;\n            })\n            .lapse(user -\u003e {\n                user.reply(\"Lapsed!\");\n                return CommandResult.PASSED;\n            });\n    }\n\n}\n```\n\nThis example would create the following command tree:\n\n```\nblob                 -\u003e Lapsed!\nblob hello \u003cboolean\u003e -\u003e Boolean %boolean%\nblob hello \u003cint\u003e     -\u003e Number %int%\nblob hello \u003cstring\u003e  -\u003e String %string%\n```\n\nThere are several built-in input types available in the `Arguments` class, covering most of the appropriate primitive\ntypes.\n\nThese patterns are automatically weighted to reduce the chances of an unwanted parse.\nFor example, an all-accepting `STRING` will always be checked after a more rigid `BOOLEAN` or `INTEGER` type.\n\n#### Greedy Inputs\n\nArguments can be marked greedy, in order to take the entire remainder of the user input.\n\n```java\nclass MyCommand extends Command\u003cUser\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\")\n            .arg(\"hello\", \"there\", (user, arguments) -\u003e {\n                user.reply(\"General Kenobi!\");\n                return CommandResult.PASSED;\n            })\n            .arg(STRING_END, (user, arguments) -\u003e {\n                user.reply(arguments.get(0));\n                return CommandResult.PASSED;\n            })\n            .lapse(user -\u003e {\n                user.reply(\"Lapsed!\");\n                return CommandResult.PASSED;\n            });\n    }\n\n}\n```\n\nThis example would create the following command tree:\n\n```\nblob                -\u003e Lapsed!\nblob hello there    -\u003e General Kenobi!\nblob \u003cstring...\u003e    -\u003e %string...%\n```\n\nGreedy arguments will eat the entire remaining input -\nthey cannot be followed by another argument since there is nothing left to parse. \\\nThese are weighted very heavily by default to try and arrange them as the last option to be checked,\nbut due to their nature they are prone to unwanted false positives and should be used carefully.\n\n### Compound Arguments\n\nThe compound argument is a special feature of Centurion that allows a command input to function like a miniature\ngrammar.\nA program may specify a compound argument, e.g. centurion-minecraft's `vector`, that is made from its own set of\nmulti-argument structures.\n\nThis argument may be used as normal within a command, e.g.\n\n```java\nclass MyCommand extends Command\u003cPlayer\u003e {\n\n    @Override\n    public Behaviour create() {\n        return this.command(\"blob\")\n            .arg(\"teleport\", VECTOR, (player, arguments) -\u003e {\n                final Vector vector = arguments.get(0);\n                player.teleportTo(vector);\n                return CommandResult.PASSED;\n            });\n    }\n\n}\n```\n\nThis example would create the following command tree:\n\n```\nblob teleport \u003cvector\u003e  -\u003e Teleports player\n```\n\nHowever, the compound `vector` argument may specify its own patterns, such as:\n\n```\n\u003cnumber\u003e meters \u003cdirection\u003e\n\u003cx\u003e \u003cy\u003e \u003cz\u003e\n```\n\nBoth of these are verified and then converted to a `Vector` using their own functions.\nThis `Vector` is then passed back as the input for the command.\n\nIn essence, the command's real tree would look like:\n\n```\nblob teleport \u003cnumber\u003e meters \u003cdirection\u003e\nblob teleport \u003cx\u003e \u003cy\u003e \u003cz\u003e\n```\n\nHowever, the command does not need to manually parse the number and direction or x, y and z to a vector.\n\nThis means that both the inputs `teleport 10 meters north` and `teleport 4 32 17` would provide a single `Vector` input\nto the same function.\n\n#### Compound Parsing Differences\n\nCompound arguments have a similar design and structure to commands themselves, and function as a sort of sub-command.\nHowever, the compound argument does not care about trailing input at the end.\n\nIf a compound looks for `\u003cnumber\u003e meters` and the user inputs `10 meters south`,\nthe remaining `south` will be sent back to the command parser to check against the next argument.\n\n### Reading Patterns\n\nThe full set of command patterns generated from a command is available in `command.patterns()`.\n\nThese follow a distinct structure:\n\n```\nlabel literal \u003crequired\u003e [optional] \u003cgreedy...\u003e\n```\n\nThe `label` is the main command. \\\nLiteral arguments have no brackets. \\\nAngle brackets are used for `\u003crequired\u003e` inputs. \\\nSquare brackets are used for `[optional]` inputs, which will pass `null` to the function if not set. \\\nAn ellipsis is used for `greedy...` inputs.\n\n### Receiving Inputs\n\nCommand argument inputs are parsed and stored in the `arguments` function parameter. \\\nThese are available by zero-index, using `arguments.get(index)`.\n\nLiteral arguments do **not** take up a slot in the argument container. \\\nThe command `/blob hello \u003cint\u003e \u003cstring\u003e` would have its `int` input at index `0` and its `string` input at index `1`.\nThere is no index for the literal `hello` argument, since this is already known before execution.\n\nArgument values are `null` __if and only if__ the argument type was `[optional]` and no input was provided.\n\nArgument values are automatically typed by the `get` method. If the type is somehow unknown (e.g. an argument type can\ngive multiple result types),\na type parameter should be specified and the result should be checked manually with `arguments.\u003cObject\u003eget(2)`.\n\nIf the wrong type is asked for, the cast will fail and throw an error.\n\n### Command Results\n\nCommands return a result with some information about its execution.\nThese have a boolean `successful()` value and a `CommandResult` enum `type()` for switching. \\\nThey may also provide a `Throwable` error that occurred when trying to dispatch, parse or run the command function.\n\n#### Failures\n\nA failure result will be marked `successful() == false`. This means that the command failed in its regular execution in\none of several ways.\n\nExample failure standards:\n\n1. No lapse behaviour was defined for the command and the input matched no argument.\n2. An uncaught error was thrown during either parsing or execution. This error is provided in the result.\n    - N.B. this may have halted a command *part way through* execution - safety checks are advised.\n3. A command function returned a failure result.\n\n#### Input Validation\n\nFunctions may apply additional validation to inputs through the use of the `WRONG_INPUT` or `LAPSE` failure results.\nThese results are a __special case__; they are _not_ a failure condition for the command execution.\n\nIf the `WRONG_INPUT` result is returned, the line of argument parsing will be abandoned and the iteration will move on.\nIf no subsequent argument patterns match the input this will fall to the lapse case.\n\nPlease note that this sort of fall-through can be dangerous and may lead to unexpected conditions being met.\\\nExempli gratia:\n\n```\ninput    test hello there\ncase #1  test hello \u003cstring\u003e  -\u003e WRONG_INPUT // continues\ncase #1  test \u003cstring...\u003e     -\u003e PASSED      // accepted here\nlapse                                        // never reaches lapse\n```\n\nAlternatively, the `LAPSE` result will break parsing and go straight to the `lapse` function.\n\n```\ninput    test hello there\ncase #1  test hello \u003cstring\u003e  -\u003e LAPSE       // jumps\ncase #1  test \u003cstring...\u003e     -\u003e             // skipped\nlapse                         -\u003e PASSED      // accepted here\n```\n\n## Motivations\n\nCenturion is the follow-on from [Commander](https://github.com/Moderocky/Commander), my previous command framework.\n\nWhile its structure is similar in some ways, it has a few key differences.\n\nCommander had a core defect: it was originally built for 'Minecraft' and then extracted and republished as a standalone\nlibrary.\nThat meant a lot of its behaviour was either idiomatic or unnecessary for other environments.\n\nCenturion was built first as a standalone framework and the 'Minecraft' and other modules were built on top of its core.\n\nCenturion also has a more intuitive framework, avoiding the nesting structure that Commander relied on.\nCenturion also supports automatic typing when retrieving an argument.\n\n### Commander\n\n```java\nclass MyCommand extends Commander {\n\n    public Command create() {\n        return command(\"test\")\n            .arg(\"hello\",\n                arg(\"there\", sender -\u003e {\n                    System.out.println(\"General Kenobi!\");\n                })\n            ).arg(\"hello\",\n                arg((sender, args) -\u003e {\n                    System.out.println(\"Hello, \" + args[0] + \"!\");\n                }, new ArgString())\n            );\n    }\n\n}\n```\n\n### Centurion\n\n```java\nclass MyCommand extends Command\u003cSender\u003e {\n\n    public Behaviour\u003cSender\u003e create() {\n        return command(\"test\")\n            .arg(\"hello\", \"there\", (sender, arguments) -\u003e {\n                System.out.println(\"General Kenobi!\");\n            })\n            .arg(\"hello\", STRING, (sender, arguments) -\u003e {\n                System.out.println(\"Hello, \" + arguments.get(0) + \"!\");\n            });\n    }\n\n}\n```\n\n## Provided Domain Support\n\nCenturion provides support for some domains out-of-the-box. This is not built in to the core library, but comes as\nseparate modules that include the core.\nThese modules can be used as dependencies in place of the core library.\n\n### Discord\n\nTBD.\n\n## Licensing\n\nThe core of Centurion is available under MIT as seen in the repository.\n\nSome classes in the 'Minecraft' submodule depend transitively on Bukkit and so are covered by GPL-3 instead.\n\n# Minecraft\n\nCommands for Minecraft's Bukkit server are available.\nThese are registered in the `minecraft` module and available through `MinecraftCommand`.\nSome basic argument types for the domain are available, along with placeholder formatting support and interaction\nutilities.\n\nThis support was built for a recent version of the game -- it will not support older versions, it may not support future\nversions without modification.\n\n## Language\n\nFor integration with translation systems, centurion aims to use translatable messages internally.\nThese can be edited or filled in via a client resource pack. If no pack is present they will default to English\nmessages.\n\nThese are used _only_ in built-in behaviour, which can be changed, overridden or removed entirely by resources using\nthis.\n\n| Lang Key                     | Default                                                         |\n|------------------------------|-----------------------------------------------------------------|\n| `centurion.tooltip.suggest`  | Click to Suggest                                                |\n| `centurion.tooltip.run`      | Click to Run                                                    |\n| `commands.help.failed`       | Unknown command or insufficient permissions (Minecraft default) |\n| `centurion.argument.\u003clabel\u003e` | (label)                                                         |\n| `centurion.text.usage`       | Usage for `%s`:                                                 |\n\n## Arguments\n\nRudimentary support is provided for the following argument types.\n\n### Block Face\n\nThe block face argument accepts identifiers from `org.bukkit.block.BlockFace`, such as cardinal directions, up, down and\npartial directions.\n\n### Material\n\nThe material argument accepts identifiers from `org.bukkit.Material`, which appear to correspond to the minecraft IDs,\nwithout the `minecraft:` namespace.\n\n### Entity Type\n\nThe entity type argument accepts identifiers from `org.bukkit.entity.EntityType`, corresponding to the minecraft entity\nIDs without their `minecraft:` namespace.\n\n### Color\n\nThe color argument accepts the legacy named colours from `net.kyori.adventure.text.format.NamedTextColor`, as well as\nhash-preceded hex colour codes like `#ff0000`.\nThese colour classes are provided from a third-party library included in PaperMC.\n\n### Block Data\n\nThe block data argument accepts a valid identifier in the form `namespace:key[property=value]`, where the namespace and\nproperty sections are optional.\nThis will only accept material types that are blocks, e.g. `stone` is acceptable but `stick` is not.\nThis will only accept material types registered with the Minecraft server, so `my_block` will not be accepted unless a\nmodification has registered it.\n\n### Player\n\nThe player argument accepts the name of an online player. It may accept an unambiguous partial name.\n\n### Selector\n\nThe selector argument parses an entity selector that can be used to pick out one or more entities from a given context,\ne.g. the command sender.\n\nThis can also parse complex selectors (e.g. `@e[distance=..10,limit=1]`) which are evaluated from the perspective of the\ncommand sender where possible.\nThese selectors are parsed by Minecraft's server.\n\n### World\n\nThe world argument accepts a world by name.\n\n### Key\n\nThe key argument accepts a resource key, such as `minecraft:stone` or `my_mod:resource/path`.\n\n### Tag\n\nTag arguments (Material, Item, Entity) accept a set of built-in tags, which select multiple of a thing, such as\nmaterials or entity types.\nThese can be used to provide blanket selection, e.g. `#raiders` picking out `Pillager`, `Vindicator`, `Witch`, etc.\n\n### Relative Number\n\nRelative numbers come in the potential format `~X`, where either the preceding tilde `~` or following number `0` are\noptional.\nA no-number value `~` equates to relative zero.\n\nRelative numbers do not have to be relative, e.g. `~10` is marked as relative whereas `10` is not.\n\nWhat the relativity of the number means depends entirely on the implementation of a command. Minecraft typically uses\nthese for relativising a position.\n\n### Local Number\n\nLocal numbers must be preceded by a local marker circumflex `^`.\nA no-number value `^` equates to local zero.\n\nWhat the locality of the number means depends entirely on the implementation of a command. Minecraft typically uses\nthese for localising a position.\n\n### Vector\n\nA vector is a quantity with direction and magnitude, representing an offset from an origin, such as `10 5 -4`.\nThese are typically used to indicate position coordinates, or the length, width and height of an area, but they are not\nattached to a world.\n\nThe vector argument accepts either three numerical values, e.g. `3.5 0 -9`, or a length with a direction,\ne.g. `10 meters north`. Both will be evaluated to a three-value vector.\n\nThe origin of the vector is left up to the implementation of a command.\n\n### Location\n\nA location is a position within a world.\n\nThe location argument accepts any of the following:\n\n```\n\u003cx\u003e \u003cy\u003e \u003cz\u003e in \u003cworld\u003e\nspawn of \u003cworld\u003e\nbed of \u003cplayer\u003e\n\u003coffset\u003e of \u003centity\u003e\n\u003coffset\u003e of \u003clocation\u003e\n```\n\nExample inputs include:\n\n```\n10 64 -45 in world_nether\nspawn of world\nbed of Mackenbee\n10 meters north of @p\n5 meters east of spawn of world\n```\n\n### Offset\n\nAn offset is a special relative form of the vector argument.\nIt supports relative numbers as values, e.g. `~ ~10 ~5` instead of fixed coordinates.\nThis relative vector can be mapped on to a position, in which case the relative `~` inputs will be added on to the new\norigin, whereas the non-relative numbers will replace the origin coordinates.\n\nThe offset argument accepts:\n\n```\n\u003cx\u003e \u003cy\u003e \u003cz\u003e\n\u003cnumber\u003e meters \u003cdirection\u003e\n```\n\nExample inputs include:\n\n```\n~ ~5 ~         // 5 meters above origin\n10 ~ -42       // 10, height of origin, -42\n5 meters down  // 5 meters below origin\n```\n\n### Local Offset\n\nA local offset represents a position relative to an origin's orientation, e.g. the direction an entity is looking in.\nThis accepts only local `^x` numbers.\n\n```\n\u003cleft\u003e \u003cup\u003e \u003cforwards\u003e\n```\n\nExample inputs include:\n\n```\n^ ^ ^10\n^-2 ^0 ^4\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoderocky%2Fcenturion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoderocky%2Fcenturion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoderocky%2Fcenturion/lists"}