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."""