diff --git a/PYREADME.md b/PYREADME.md new file mode 100644 index 0000000..c4c539f --- /dev/null +++ b/PYREADME.md @@ -0,0 +1,27 @@ +# Fira +**Fira** is the main backend and API code for the minigame in [Unscripted](https://unscripted.marquiskurt.net), a visual novel about software development. Fira provides many facets of the minigame, including a public API that players can use to code solutions to the minigame puzzles, a configuration and data generator from level files, and a virtual machine that runs low-level code that the minigame processes (NadiaVM). Fira is named after Fira Sans, one of the game's characters. + +## Getting started +Fira comes pre-packaged in Unscripted but can be installed outside of the game to work better with IDEs and other Python tools such as Poetry. + +## Usage +For players installing this package to solve minigame puzzles, using the Fira package to access the API is relatively straightforward: + +```py +from uvn_fira.api import get_level_information, CSPlayer, CSWorld + +gp, gw = get_level_information(0, + fn_path=renpy.config.savedir + "/minigame", + exists=renpy.loadable, + load=renpy.exports.file) +``` + +Documentation on the API is located inside of Unscripted by going to **Help › Minigame** or **Settings › Minigame**. + +The documentation for the entire package is located at [https://fira.marquiskurt.net](https://fira.marquiskurt.net), which is useful for developers that wish to make custom toolkits that connect to the minigame's virtual machine or for modders that wish to make custom minigame levels. + +## Reporting bugs +Bugs and feature requests for Fira can be submitted on GitHub. + +## License +The Fira package is licensed under the Mozilla Public License v2.0. \ No newline at end of file diff --git a/README.md b/README.md index 7fa64a8..bdd8734 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fira -**Fira** is the main backend and API code for the minigame in [Unscripted](https://unscripted.marquiskurt.net), a visual novel about software development. Fira provides many facets of the minigame, including a public API that players can use to code solutions to the minigame puzzles, a configuration and data generator from level files, and a virtual machine that runs low-level code that the minigame processes. Fira is named after Fira Sans, one of the game's characters. +**Fira** is the main backend and API code for the minigame in [Unscripted](https://unscripted.marquiskurt.net), a visual novel about software development. Fira provides many facets of the minigame, including a public API that players can use to code solutions to the minigame puzzles, a configuration and data generator from level files, and a virtual machine that runs low-level code that the minigame processes (NadiaVM). Fira is named after Fira Sans, one of the game's characters. [![MPL](https://img.shields.io/github/license/alicerunsonfedora/fira)](LICENSE.txt) @@ -49,7 +49,7 @@ from uvn_fira.api import get_level_information, CSPlayer, CSWorld gp, gw = get_level_information(0, fn_path=renpy.config.savedir + "/minigame", exists=renpy.loadable, - load=renpy.exports.file) # type: (CSPlayer, CSWorld) + load=renpy.exports.file) ``` Documentation on the API is located inside of Unscripted by going to **Help › Minigame** or **Settings › Minigame**. @@ -57,9 +57,7 @@ Documentation on the API is located inside of Unscripted by going to **Help &rsa The documentation for the entire package is located at [https://fira.marquiskurt.net](https://fira.marquiskurt.net), which is useful for developers that wish to make custom toolkits that connect to the minigame's virtual machine or for modders that wish to make custom minigame levels. ## Reporting bugs - -Bugs and feature requests for Fira can be submitted on YouTrack. [Submit a report ›](https://youtrack.marquiskurt.net/youtrack/newIssue?project=FIRA) +Bugs and feature requests for Fira can be submitted on GitHub. ## License - The Fira package is licensed under the Mozilla Public License v2.0. \ No newline at end of file diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 0ed5390..ba18152 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,6 +1,6 @@ GEM specs: - activesupport (6.0.2.1) + activesupport (6.0.2.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -25,7 +25,7 @@ GEM ffi (>= 1.3.0) eventmachine (1.2.7) execjs (2.7.0) - faraday (1.0.0) + faraday (1.0.1) multipart-post (>= 1.2, < 3) ffi (1.12.2) forwardable-extended (2.6.0) @@ -205,7 +205,7 @@ GEM multipart-post (2.1.1) nokogiri (1.10.9) mini_portile2 (~> 2.4.0) - octokit (4.17.0) + octokit (4.18.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) pathutil (0.16.2) @@ -215,9 +215,9 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) rouge (3.13.0) - ruby-enum (0.7.2) + ruby-enum (0.8.0) i18n - rubyzip (2.2.0) + rubyzip (2.3.0) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) @@ -232,9 +232,9 @@ GEM thread_safe (0.3.6) typhoeus (1.3.1) ethon (>= 0.9.0) - tzinfo (1.2.6) + tzinfo (1.2.7) thread_safe (~> 0.1) - unicode-display_width (1.6.1) + unicode-display_width (1.7.0) zeitwerk (2.3.0) PLATFORMS diff --git a/docs/core/vm.md b/docs/core/vm.md index 321b740..dc46382 100644 --- a/docs/core/vm.md +++ b/docs/core/vm.md @@ -154,6 +154,28 @@ Move the player in a given direction. ### collect() Collect. In the VM, this acts like a pause. +### add() + +Add the two topmost values on the stack. + +### sub() + +Subtract the two topmost values on the stack. + +### mult() + +Multiply the two topmost values on the stack. + +### div() + +Divide the two topmost values on the stack. + +### neg() + +Negate the topmost value on the stack. + +Effectively, this is the equivalent of pushing -1 onto the stack and calling [`mult`](#mult). + ### exit() Try to exit the world and end execution of the script. @@ -233,6 +255,28 @@ Move the player in a given direction. ### collect() Collect. In the VM, this acts like a pause. +### add() + +Add the two topmost values on the stack. + +### sub() + +Subtract the two topmost values on the stack. + +### mult() + +Multiply the two topmost values on the stack. + +### div() + +Divide the two topmost values on the stack. + +### neg() + +Negate the topmost value on the stack. + +Effectively, this is the equivalent of pushing -1 onto the stack and calling [`mult`](#mult). + ### exit() Try to exit the world and end execution of the script. diff --git a/docs/implementation.md b/docs/implementation.md index 2fef09f..bd61ab9 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -58,6 +58,11 @@ NadiaVM comes with several commands used to perform tasks in the minigame: - `set constant (any)`: Set the top of the stack to a constant value. - `move player (str)`: Move the player in a given direction. - `exit player`: Try to end the execution script and finish the level. +- New `add`: Add the two topmost values on the stack. +- New `sub`: Subtract the two topmost values on the stack. +- New `mult`: Multiply the two topmost values on the stack. +- New `div`: Divide the two topmost values on the stack. +- New `neg`: Negate the topmost value on the stack. Effectively the same as pushing `-1` on the stack and calling `mult`. ### Limitations diff --git a/pyproject.toml b/pyproject.toml index 36c1682..5e8d7d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [tool.poetry] name = "uvn-fira" -version = "1.2.11" +version = "1.3.0" description = "The backend and API code for the Unscripted mini-game." authors = ["Marquis Kurt "] license = "MPL-2.0" -readme = "README.md" +readme = "PYREADME.md" homepage = "https://github.com/UnscriptedVN/fira" documentation = "https://fira.marquiskurt.net" diff --git a/uvn_fira/api/info.py b/uvn_fira/api/info.py index 1a5b0e8..5891b66 100644 --- a/uvn_fira/api/info.py +++ b/uvn_fira/api/info.py @@ -43,7 +43,7 @@ def get_level_information(level, fn_path="", **kwargs): if "config_file" in kwargs \ else "core/minigame/levels/level%s.toml" % (level) w_info = CSWorldConfigReader(conf, **kwargs) - writer = CSNadiaVMWriter(fn_path + "/compiled/lvl%s.nvm" % (level)) + writer = CSNadiaVMWriter(fn_path + "/compiled/adv_lvl%s.nvm" % (level)) world = CSWorld(from_data=w_info.data) player = CSPlayer(in_world=world, vm=writer) return player, world diff --git a/uvn_fira/core/template.py b/uvn_fira/core/template.py index d88829e..3610214 100644 --- a/uvn_fira/core/template.py +++ b/uvn_fira/core/template.py @@ -35,8 +35,9 @@ def generate_template(filepath, for_level=0): # to view the limitations of using the official API as called here. # # If you want to use a third-party tool or framework instead of the official Fira API and want to -# sideload in a virtual machine file, do not write any code in this file. More information can be -# found at https://fira.marquiskurt.net/implementation.html#obtaining-code-from-third-party-tools. +# sideload in a virtual machine file, make sure "Force Python compiler" in Settings > Minigame is +# disabled and that your tool compiles the NadiaVM file to your save directory like the following: +# /path/to/RenPy/net.marquiskurt.unscripted/minigame/compiled/adv_lvl%s.nvm # # Import the level information APIs. @@ -46,9 +47,9 @@ def generate_template(filepath, for_level=0): game_player, game_world = get_level_information(%s, fn_path=renpy.config.savedir + "/minigame/", exists=renpy.loadable, - load=renpy.exports.file) # type: (CSPlayer, CSWorld) + load=renpy.exports.file) # WRITE CODE HERE -""" % (for_level, for_level) +""" % (for_level, for_level, for_level) with open(filepath, 'w+') as file_obj: file_obj.write(template) diff --git a/uvn_fira/core/vm.py b/uvn_fira/core/vm.py index a26775d..36191fa 100644 --- a/uvn_fira/core/vm.py +++ b/uvn_fira/core/vm.py @@ -30,6 +30,12 @@ - `set constant (any)`: Set the top of the stack to a constant value. - `move player (str)`: Move the player in a given direction. - `exit player`: Try to end the execution script and finish the level. +- `add`: Add the two topmost values on the stack. +- `sub`: Subtract the two topmost values on the stack. +- `mult`: Multiply the two topmost values on the stack. +- `div`: Divide the two topmost values on the stack. +- `neg`: Negate the topmost value on the stack. Effectively the same as pushing `-1` on the stack + and calling `mult`. """ from typing import Optional @@ -86,21 +92,34 @@ def next(self): """ current = self._instructions.pop(0).split(" ") - if current[0] == "alloc": - self._alloc(current[1], int(current[2])) - elif current[0] == "push": - self._push(current[1], int(current[2])) - elif current[0] == "pop": - self._pop(current[1], int(current[2])) - elif current[0] == "set": - self._set(current[2]) - elif current[0] == "move": - self._move(current[2]) - elif current[0] == "collect": - pass - elif current[0] != "exit": + instruct = { + "alloc": self._alloc, + "push": self._push, + "pop" : self._pop, + "set": self._set, + "move": self._move, + "add": self._add, + "sub": self._sub, + "mult": self._mult, + "div": self._div, + "neg": self._neg, + "exit": "NOCOMM", + "collect": "NOCOMM", + } + + if current[0] not in instruct: raise CSNadiaVMCommandNotFoundError("Invalid command: '%s'" % (current[0])) + command = instruct.get(current[0]) + if current[0] in ["alloc", "push", "pop"]: + command(current[1], int(current[2])) + elif current[0] in ["set", "move"]: + command(current[2]) + elif current[0] in ["add", "sub", "mult", "div", "neg"]: + command() + else: + pass + def _alloc(self, name, size): """Allocate a space of memory for a given array. @@ -155,6 +174,38 @@ def _move(self, direction): curr_x, curr_y = self._player_pos self._player_pos = curr_x + trans_x, curr_y + trans_y + def _add(self): + """Add the two topmost values on the stack.""" + x = self._stack.pop() + y = self._stack.pop() + self._stack.append(int(x) + int(y)) + + def _sub(self): + """Subtract the two topmost values on the stack.""" + x = self._stack.pop() + y = self._stack.pop() + self._stack.append(int(x) - int(y)) + + def _mult(self): + """Multiply the two topmost values on the stack.""" + x = self._stack.pop() + y = self._stack.pop() + self._stack.append(int(x) * int(y)) + + def _div(self): + """Divide the two topmost values on the stack.""" + x = self._stack.pop() + y = self._stack.pop() + self._stack.append(int(x) / int(y)) + + def _neg(self): + """Negate the topmost value on the stack. + + Effectively, this is the equivalent of pushing -1 onto the stack and calling mult. + """ + self._stack.append(-1) + self._mult() + def get(self, name): # type: (CSNadiaVM, str) -> Optional[list] """Get the specified item in the virtual machine. @@ -265,6 +316,29 @@ def exit(self): """Try to exit the world and end execution of the script.""" self.instructions.append("exit player\n") + def add(self): + """Add the two topmost values on the stack.""" + self.instructions.append("add\n") + + def sub(self): + """Subtract the two topmost values on the stack.""" + self.instructions.append("sub\n") + + def mult(self): + """Multiply the two topmost values on the stack.""" + self.instructions.append("mult\n") + + def div(self): + """Divide the two topmost values on the stack.""" + self.instructions.append("div\n") + + def neg(self): + """Negate the topmost value on the stack. + + Effectively, this is the equivalent of pushing -1 onto the stack and calling mult. + """ + self.instructions.append("neg\n") + def write(self): # type: (CSNadiaVMWriterBuilder) -> None """Write the VM code to the requested file.""" @@ -367,6 +441,29 @@ def exit(self): """Try to exit the world and end execution of the script.""" self.code += "exit player\n" + def add(self): + """Add the two topmost values on the stack.""" + self.code += "add\n" + + def sub(self): + """Subtract the two topmost values on the stack.""" + self.code += "sub\n" + + def mult(self): + """Multiply the two topmost values on the stack.""" + self.code += "mult\n" + + def div(self): + """Divide the two topmost values on the stack.""" + self.code += "div\n" + + def neg(self): + """Negate the topmost value on the stack. + + Effectively, this is the equivalent of pushing -1 onto the stack and calling mult. + """ + self.code += "neg\n" + def write(self): # type: (CSNadiaVMWriter) -> None """Write the VM code to the requested file."""