diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..184c4eb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.testing.unittestArgs": [ + "-v", + "-s", + ".", + "-p", + "test_*.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true +} \ No newline at end of file diff --git a/bink/native/arm64/libbink.dylib b/bink/native/arm64/libbink.dylib index b0676b2..5a46ec4 100755 Binary files a/bink/native/arm64/libbink.dylib and b/bink/native/arm64/libbink.dylib differ diff --git a/bink/native/arm64/libbink.so b/bink/native/arm64/libbink.so index 8705713..35218a4 100755 Binary files a/bink/native/arm64/libbink.so and b/bink/native/arm64/libbink.so differ diff --git a/bink/native/x86_64/bink.dll b/bink/native/x86_64/bink.dll index c38d15d..4255b09 100644 Binary files a/bink/native/x86_64/bink.dll and b/bink/native/x86_64/bink.dll differ diff --git a/bink/native/x86_64/libbink.dylib b/bink/native/x86_64/libbink.dylib index c497215..e67afc3 100755 Binary files a/bink/native/x86_64/libbink.dylib and b/bink/native/x86_64/libbink.dylib differ diff --git a/bink/native/x86_64/libbink.so b/bink/native/x86_64/libbink.so index e7f6843..bad4b7c 100755 Binary files a/bink/native/x86_64/libbink.so and b/bink/native/x86_64/libbink.so differ diff --git a/bink/story.py b/bink/story.py index 3e40bf3..d0803d4 100644 --- a/bink/story.py +++ b/bink/story.py @@ -136,6 +136,40 @@ def choose_path_string(self, path: str): LIB.bink_cstring_free(err_msg) raise RuntimeError(err) + def save_state(self) -> str: + """Saves the current state of the story and returns it as a string. + The returned state can be loaded later using load_state().""" + err_msg = ctypes.c_char_p() + save_string = ctypes.c_char_p() + ret = LIB.bink_story_save_state( + self._story, + ctypes.byref(save_string), + ctypes.byref(err_msg)) + + if ret != BINK_OK: + err = err_msg.value.decode('utf-8') + LIB.bink_cstring_free(err_msg) + raise RuntimeError(err) + + result = save_string.value.decode('utf-8') + LIB.bink_cstring_free(save_string) + + return result + + def load_state(self, save_state: str): + """Loads a previously saved state into the story. + This allows resuming the story from a saved point.""" + err_msg = ctypes.c_char_p() + ret = LIB.bink_story_load_state( + self._story, + save_state.encode('utf-8'), + ctypes.byref(err_msg)) + + if ret != BINK_OK: + err = err_msg.value.decode('utf-8') + LIB.bink_cstring_free(err_msg) + raise RuntimeError(err) + def __del__(self): LIB.bink_story_free(self._story) diff --git a/inkfiles/runtime/load-save.ink b/inkfiles/runtime/load-save.ink new file mode 100644 index 0000000..ccd6921 --- /dev/null +++ b/inkfiles/runtime/load-save.ink @@ -0,0 +1,28 @@ +-> back_in_london + +=== back_in_london === + +We arrived into London at 9.45pm exactly. + +* "There is not a moment to lose!"[] I declared. + -> hurry_outside + +* "Monsieur, let us savour this moment!"[] I declared. + My master clouted me firmly around the head and dragged me out of the door. + -> dragged_outside + +* [We hurried home] -> hurry_outside + + +=== hurry_outside === +We hurried home to Savile Row -> as_fast_as_we_could + + +=== dragged_outside === +He insisted that we hurried home to Savile Row +-> as_fast_as_we_could + + +=== as_fast_as_we_could === +<> as fast as we could. +-> END diff --git a/inkfiles/runtime/load-save.ink.json b/inkfiles/runtime/load-save.ink.json new file mode 100644 index 0000000..a5b5767 --- /dev/null +++ b/inkfiles/runtime/load-save.ink.json @@ -0,0 +1 @@ +{"inkVersion":21,"root":[[{"->":"back_in_london"},["done",{"#n":"g-0"}],null],"done",{"back_in_london":[["^We arrived into London at 9.45pm exactly.","\n",["ev",{"^->":"back_in_london.0.2.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.^.c-0","flg":18},{"s":["^\"There is not a moment to lose!\"",{"->":"$r","var":true},null]}],["ev",{"^->":"back_in_london.0.3.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.^.c-1","flg":18},{"s":["^\"Monsieur, let us savour this moment!\"",{"->":"$r","var":true},null]}],"ev","str","^We hurried home","/str","/ev",{"*":".^.c-2","flg":20},{"c-0":["ev",{"^->":"back_in_london.0.c-0.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.2.s"},[{"#n":"$r2"}],"^ I declared.","\n",{"->":"hurry_outside"},{"#f":5}],"c-1":["ev",{"^->":"back_in_london.0.c-1.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.3.s"},[{"#n":"$r2"}],"^ I declared.","\n","^My master clouted me firmly around the head and dragged me out of the door.","\n",{"->":"dragged_outside"},{"#f":5}],"c-2":["^ ",{"->":"hurry_outside"},"\n",{"#f":5}]}],null],"hurry_outside":["^We hurried home to Savile Row ",{"->":"as_fast_as_we_could"},"\n",null],"dragged_outside":["^He insisted that we hurried home to Savile Row","\n",{"->":"as_fast_as_we_could"},null],"as_fast_as_we_could":["<>","^ as fast as we could.","\n","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index bfbb4b3..3a6bd66 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = bink -version = 0.4.0 +version = 0.5.0 author = Rafael Garcia description = Runtime for Ink, a scripting language for writing interactive narrative long_description = file: README.rst diff --git a/tests/test_story.py b/tests/test_story.py index f0f8abe..861b331 100644 --- a/tests/test_story.py +++ b/tests/test_story.py @@ -8,6 +8,47 @@ def test_oneline(self): self.assertTrue(story.can_continue()) self.assertEqual(story.cont(), "Line.\n") + def test_load_save(self): + """Test save_state and load_state functionality.""" + # Create a story and get initial text + story = story_from_file("inkfiles/runtime/load-save.ink.json") + + # Continue to get all initial text + lines = story.continue_maximally() + + # Check first line + self.assertEqual(lines, "We arrived into London at 9.45pm exactly.\n") + + # Save the game state + save_string = story.save_state() + + print(f"Save state: {save_string}") + + # Free the current story and create a new one + del story + + story = story_from_file("inkfiles/runtime/load-save.ink.json") + + # Load the saved state + story.load_state(save_string) + + # Choose first choice + story.choose_choice_index(0) + + # Continue to get the next text + lines = story.continue_maximally() + + # The text should contain both lines we expect + self.assertIn("\"There is not a moment to lose!\" I declared.", lines) + self.assertIn("We hurried home to Savile Row as fast as we could.", lines) + + # Check that we are at the end + self.assertFalse(story.can_continue()) + + # Check that there are no more choices + choices = story.get_current_choices() + self.assertEqual(len(choices), 0) + def test_the_intercept(self): story = story_from_file("inkfiles/TheIntercept.ink.json") self.assertTrue(story.can_continue()) @@ -25,7 +66,7 @@ def test_the_intercept(self): if choices: for i, text in enumerate(choices): print(f"{i + 1}. {text}") - + # Always choose the first option story.choose_choice_index(0) else: diff --git a/update-natives.sh b/update-natives.sh index b1de43c..0707bf9 100755 --- a/update-natives.sh +++ b/update-natives.sh @@ -12,6 +12,8 @@ fi mkdir -p build cd build +echo "Downloading Version $VERSION..." + curl -kOL https://github.com/bladecoder/blade-ink-ffi/releases/download/${VERSION}/libbink-${VERSION}-aarch64-apple-darwin.tar.gz curl -kOL https://github.com/bladecoder/blade-ink-ffi/releases/download/${VERSION}/libbink-${VERSION}-aarch64-unknown-linux-gnu.tar.gz curl -kOL https://github.com/bladecoder/blade-ink-ffi/releases/download/${VERSION}/libbink-${VERSION}-x86_64-apple-darwin.tar.gz