diff --git a/LICENSE b/LICENSE index f35c0cb..bd8b823 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -PerformanC's Custom License +PerformanC's PerforVNMaker Custom License Copyright (c) 2023 PerformanC @@ -8,10 +8,12 @@ and it can even be used for commercial purposes. However, the software code neither can be used to train a neural network, nor any part of the code can be copied in any way without the permission of the PerformanC Organization team members. An exception is made for the -generated code, which can be used without only one exception: the generated -code cannot be used for other code generations or any software that creates apps. +generated code, which can be used with one exception: the generated +code cannot be used for or any type of software that creates softwares +of any kind. -The license can be included at the source code of the PerformanC software, although it is not required. +The license must included at the source code of the PerformanC software +outside the official PerformanC repository. The Software is given "as is" and without any warranties, and its developers disclaim all liability for any harm it (The Software) may cause. \ No newline at end of file diff --git a/OS_SUPPORT.md b/OS_SUPPORT.md index a891f48..30e210d 100644 --- a/OS_SUPPORT.md +++ b/OS_SUPPORT.md @@ -134,6 +134,14 @@ Achievements are a way to reward the player for doing something in the game, lik \>= `1.23.0 & v1.21.0`: Supported +##### Items + +Items are a way to reward the player for doing something in the game, like finishing the game, or finding a secret. Allows also to unlock custom paths. + +###### Version support + +\>= `1.23.0 & v1.21.0`: Supported + ### Menu The menu is the place where the player can access the settings, the About menu, and start the VN. @@ -248,9 +256,9 @@ This is the list of features that we're planning to add (or modify) to PerforVNM - [x] Custom paths (Completed) - [x] Custom Views (Completed) - [x] Achievements (Completed) +- [x] Items (Completed) - [x] Menu (Missing vertical footer) - [x] Settings (Misses some additional configurations) -- [ ] Inventory ## Code generation support diff --git a/SECURITY.md b/SECURITY.md index 68076bb..2b79570 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,9 +6,8 @@ This is the Security Policy of the PerforVNMaker project. Made to ensure the qua | Version | Supported | | --------------------------- | ------------------ | -| > 1.16.2-b.0 (1.14.8-b.0) | :white_check_mark: | -| 1.16.2-b.0 (1.14.7-b.0) | :white_check_mark: | -| < 1.16.2-b.0 (1.14.6-b.0) | :x: | +| >= v1.22.0 - v1.20.0 | :white_check_mark: | +| < v1.22.0 - v1.20.0 | :x: | Observation: This can be dismissed if it's proved that the newest versions don't fix the vulnerability. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 60f9043..0000000 --- a/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ -# TODO - -- (Customizability) Allow to remove footer and add buttons manually. -- (Code quality) Only add necessary `import`s. -- (Optimization) Create functions for stopping the music and sound effects. diff --git a/android/app/src/main/java/com/perforvnm/MainActivity.kt b/android/app/src/main/java/com/perforvnm/MainActivity.kt index 5611242..4d32896 100644 --- a/android/app/src/main/java/com/perforvnm/MainActivity.kt +++ b/android/app/src/main/java/com/perforvnm/MainActivity.kt @@ -11,6 +11,7 @@ import android.os.Looper import android.app.Activity import android.util.TypedValue import android.media.MediaPlayer +import android.widget.Toast import android.widget.TextView import android.widget.ImageView import android.widget.ScrollView @@ -37,8 +38,10 @@ import android.content.Context import android.content.SharedPreferences class MainActivity : Activity() { - private var scenes = MutableList(5) { 0 } + private var scenes = MutableList(6) { 0 } private var scenesLength = 1 + private var items = MutableList(1) { 0 } + private var itemsLength = 0 private val handler = Handler(Looper.getMainLooper()) private var textSpeed = 50L private var sEffectVolume = 1f @@ -1204,6 +1207,12 @@ class MainActivity : Activity() { scenes.set(j, historyScenes.getInt(j)) } scenesLength = historyScenes.length() + + val sceneItems = buttonData.getJSONArray("items") + for (j in 0 until sceneItems.length()) { + items.set(j, sceneItems.getInt(j)) + } + itemsLength = sceneItems.length() switchScene(buttonData.getInt("scene")) } @@ -1256,6 +1265,17 @@ class MainActivity : Activity() { } } } + 969329308 -> { + when (characterData.getString("name")) { + "Pedro" -> { + imageViewCharacter.setImageResource(R.raw.pedro_staring) + + val leftDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._5sdp) + + layoutParamsImageViewCharacter.setMargins(leftDpLoad + leftDpCharacter, topDpLoad, 0, 0) + } + } + } 1722916383 -> { when (characterData.getString("name")) { "Pedro" -> { @@ -1655,7 +1675,7 @@ class MainActivity : Activity() { var saves = inputStream.bufferedReader().use { it.readText() } inputStream.close() - val newSave = "{\"scene\":1722916382,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + "}" + val newSave = "{\"scene\":1722916382,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + ",\"items\":" + itemsToJson() + "}" if (saves == "[]") saves = "[" + newSave + "]" else saves = saves.dropLast(1) + "," + newSave + "]" @@ -1684,6 +1704,16 @@ class MainActivity : Activity() { buttonMenu.layoutParams = layoutParamsMenu buttonMenu.setOnClickListener { + for (j in 0 until scenesLength) { + scenes.set(j, 0) + } + scenesLength = 0 + + for (j in 0 until itemsLength) { + items.set(j, 0) + } + itemsLength = 0 + if (mediaPlayer != null) { mediaPlayer!!.stop() mediaPlayer!!.release() @@ -1731,6 +1761,13 @@ class MainActivity : Activity() { buttonSubScenes.layoutParams = layoutParamsSubScenes buttonSubScenes.setOnClickListener { + if (!items.contains(1789536792)) { + Toast.makeText(this, "You don't have the required item.", Toast.LENGTH_SHORT).show() + items.remove(1789536792) + + return@setOnClickListener + } + if (mediaPlayer != null) { mediaPlayer!!.stop() mediaPlayer!!.release() @@ -1780,6 +1817,9 @@ class MainActivity : Activity() { giveAchievement(1183596997) + items.set(itemsLength, 1789536792) + itemsLength++ + setContentView(frameLayout) } @@ -1909,7 +1949,7 @@ class MainActivity : Activity() { var saves = inputStream.bufferedReader().use { it.readText() } inputStream.close() - val newSave = "{\"scene\":1722916385,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + "}" + val newSave = "{\"scene\":1722916385,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + ",\"items\":" + itemsToJson() + "}" if (saves == "[]") saves = "[" + newSave + "]" else saves = saves.dropLast(1) + "," + newSave + "]" @@ -1938,6 +1978,16 @@ class MainActivity : Activity() { buttonMenu.layoutParams = layoutParamsMenu buttonMenu.setOnClickListener { + for (j in 0 until scenesLength) { + scenes.set(j, 0) + } + scenesLength = 0 + + for (j in 0 until itemsLength) { + items.set(j, 0) + } + itemsLength = 0 + handler.removeCallbacksAndMessages(null) findViewById(android.R.id.content).setOnClickListener(null) @@ -1991,18 +2041,22 @@ class MainActivity : Activity() { frameLayout.addView(buttonBack) - setContentView(frameLayout) - findViewById(android.R.id.content).setOnClickListener { handler.removeCallbacksAndMessages(null) + findViewById(android.R.id.content).setOnClickListener(null) - it.setOnClickListener(null) scenes.set(scenesLength, 1722916385) scenesLength++ - scene5() + if (!items.contains(1789536792)) { + no_items() + } else { + scene5() + } } + + setContentView(frameLayout) } private fun scene5() { @@ -2127,7 +2181,7 @@ class MainActivity : Activity() { var saves = inputStream.bufferedReader().use { it.readText() } inputStream.close() - val newSave = "{\"scene\":1722916386,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + "}" + val newSave = "{\"scene\":1722916386,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + ",\"items\":" + itemsToJson() + "}" if (saves == "[]") saves = "[" + newSave + "]" else saves = saves.dropLast(1) + "," + newSave + "]" @@ -2156,6 +2210,16 @@ class MainActivity : Activity() { buttonMenu.layoutParams = layoutParamsMenu buttonMenu.setOnClickListener { + for (j in 0 until scenesLength) { + scenes.set(j, 0) + } + scenesLength = 0 + + for (j in 0 until itemsLength) { + items.set(j, 0) + } + itemsLength = 0 + handler.removeCallbacksAndMessages(null) findViewById(android.R.id.content).setOnClickListener(null) @@ -2210,6 +2274,223 @@ class MainActivity : Activity() { setContentView(frameLayout) } + private fun no_items() { + val frameLayout = FrameLayout(this) + frameLayout.setBackgroundColor(0xFF000000.toInt()) + + val imageView_scenario = ImageView(this) + imageView_scenario.setImageResource(R.raw.background_thanking) + imageView_scenario.scaleType = ImageView.ScaleType.FIT_CENTER + + frameLayout.addView(imageView_scenario) + + val imageView_Pedro = ImageView(this) + imageView_Pedro.setImageResource(R.raw.pedro_staring) + imageView_Pedro.scaleType = ImageView.ScaleType.FIT_CENTER + + val layoutParams_Pedro = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + + layoutParams_Pedro.gravity = Gravity.CENTER + layoutParams_Pedro.setMargins(resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._20sdp), 0, 0, 0) + + imageView_Pedro.layoutParams = layoutParams_Pedro + + frameLayout.addView(imageView_Pedro) + + val rectangleViewSpeech = RectangleView(this) + + val sdp53 = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._53sdp) + + val layoutParamsRectangleSpeech = LayoutParams(LayoutParams.WRAP_CONTENT, sdp53) + layoutParamsRectangleSpeech.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + + rectangleViewSpeech.layoutParams = layoutParamsRectangleSpeech + rectangleViewSpeech.setAlpha(0.8f) + rectangleViewSpeech.setColor(0xFF000000.toInt()) + + frameLayout.addView(rectangleViewSpeech) + + val textViewSpeech = TextView(this) + textViewSpeech.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(com.intuit.ssp.R.dimen._9ssp)) + textViewSpeech.setTextColor(0xFFFFFFFF.toInt()) + + val layoutParamsSpeech = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + + layoutParamsSpeech.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL + layoutParamsSpeech.setMargins(0, resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._270sdp), 0, 0) + + textViewSpeech.layoutParams = layoutParamsSpeech + + var speechText = "\"You don't have the item, sorry.\"" + var i = 0 + + handler.postDelayed(object : Runnable { + override fun run() { + if (i < speechText.length) { + textViewSpeech.text = speechText.substring(0, i + 1) + i++ + handler.postDelayed(this, textSpeed) + } + } + }, textSpeed) + + frameLayout.addView(textViewSpeech) + + val rectangleViewAuthor = RectangleView(this) + + val layoutParamsRectangleAuthor = LayoutParams(LayoutParams.WRAP_CONTENT, 70) + layoutParamsRectangleAuthor.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + layoutParamsRectangleAuthor.setMargins(0, 0, 0, sdp53) + + rectangleViewAuthor.layoutParams = layoutParamsRectangleAuthor + rectangleViewAuthor.setAlpha(0.6f) + rectangleViewAuthor.setColor(0xFF000000.toInt()) + + frameLayout.addView(rectangleViewAuthor) + + val textViewAuthor = TextView(this) + textViewAuthor.text = "Pedro" + textViewAuthor.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(com.intuit.ssp.R.dimen._13ssp)) + textViewAuthor.setTextColor(0xFFFFFFFF.toInt()) + + val layoutParamsAuthor = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + + layoutParamsAuthor.gravity = Gravity.BOTTOM or Gravity.START + layoutParamsAuthor.setMargins(resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._155sdp), 0, 0, sdp53) + + textViewAuthor.layoutParams = layoutParamsAuthor + + frameLayout.addView(textViewAuthor) + + val ssp8 = resources.getDimension(com.intuit.ssp.R.dimen._8ssp) + + val buttonSave = Button(this) + buttonSave.text = "Save" + buttonSave.setTextSize(TypedValue.COMPLEX_UNIT_PX, ssp8) + buttonSave.setTextColor(0xFFFFFFFF.toInt()) + buttonSave.background = null + + val layoutParamsSave = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + + val sdp15 = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._15sdp) + + layoutParamsSave.gravity = Gravity.TOP or Gravity.START + layoutParamsSave.setMargins(sdp15, 0, 0, 0) + + buttonSave.layoutParams = layoutParamsSave + + buttonSave.setOnClickListener { + val inputStream = openFileInput("saves.json") + var saves = inputStream.bufferedReader().use { it.readText() } + inputStream.close() + + val newSave = "{\"scene\":969329308,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + ",\"items\":" + itemsToJson() + "}" + + if (saves == "[]") saves = "[" + newSave + "]" + else saves = saves.dropLast(1) + "," + newSave + "]" + + val outputStream = openFileOutput("saves.json", Context.MODE_PRIVATE) + outputStream.write(saves.toByteArray()) + outputStream.close() + } + + frameLayout.addView(buttonSave) + + val buttonMenu = Button(this) + buttonMenu.text = "Menu" + buttonMenu.setTextSize(TypedValue.COMPLEX_UNIT_PX, ssp8) + buttonMenu.setTextColor(0xFFFFFFFF.toInt()) + buttonMenu.background = null + + val layoutParamsMenu = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + + layoutParamsMenu.gravity = Gravity.TOP or Gravity.START + layoutParamsMenu.setMargins(sdp15, resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._23sdp), 0, 0) + + buttonMenu.layoutParams = layoutParamsMenu + + buttonMenu.setOnClickListener { + for (j in 0 until scenesLength) { + scenes.set(j, 0) + } + scenesLength = 0 + + for (j in 0 until itemsLength) { + items.set(j, 0) + } + itemsLength = 0 + + handler.removeCallbacksAndMessages(null) + + findViewById(android.R.id.content).setOnClickListener(null) + + mediaPlayer = MediaPlayer.create(this, R.raw.menu_music) + + if (mediaPlayer != null) { + mediaPlayer!!.start() + + val volume = getSharedPreferences("VNConfig", Context.MODE_PRIVATE).getFloat("musicVolume", 1f) + mediaPlayer!!.setVolume(volume, volume) + + mediaPlayer!!.setOnCompletionListener { + mediaPlayer!!.start() + } + } + + menu() + } + + frameLayout.addView(buttonMenu) + + val buttonBack = Button(this) + buttonBack.text = "Back" + buttonBack.setTextSize(TypedValue.COMPLEX_UNIT_PX, ssp8) + buttonBack.setTextColor(0xFFFFFFFF.toInt()) + buttonBack.background = null + + val layoutParamsBack = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + + layoutParamsBack.gravity = Gravity.TOP or Gravity.START + layoutParamsBack.setMargins(sdp15, resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._46sdp), 0, 0) + + buttonBack.layoutParams = layoutParamsBack + + buttonBack.setOnClickListener { + handler.removeCallbacksAndMessages(null) + + findViewById(android.R.id.content).setOnClickListener(null) + + val scene = scenes.get(scenesLength - 1) + + scenesLength-- + scenes.set(scenesLength, 0) + + switchScene(scene) + } + + frameLayout.addView(buttonBack) + + setContentView(frameLayout) + } + private fun scene2(animate: Boolean) { val frameLayout = FrameLayout(this) frameLayout.setBackgroundColor(0xFF000000.toInt()) @@ -2365,7 +2646,7 @@ class MainActivity : Activity() { var saves = inputStream.bufferedReader().use { it.readText() } inputStream.close() - val newSave = "{\"scene\":1722916383,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + "}" + val newSave = "{\"scene\":1722916383,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + ",\"items\":" + itemsToJson() + "}" if (saves == "[]") saves = "[" + newSave + "]" else saves = saves.dropLast(1) + "," + newSave + "]" @@ -2394,6 +2675,16 @@ class MainActivity : Activity() { buttonMenu.layoutParams = layoutParamsMenu buttonMenu.setOnClickListener { + for (j in 0 until scenesLength) { + scenes.set(j, 0) + } + scenesLength = 0 + + for (j in 0 until itemsLength) { + items.set(j, 0) + } + itemsLength = 0 + handler.removeCallbacksAndMessages(null) findViewById(android.R.id.content).setOnClickListener(null) @@ -2443,6 +2734,10 @@ class MainActivity : Activity() { frameLayout.addView(buttonBack) findViewById(android.R.id.content).setOnClickListener { + handler.removeCallbacksAndMessages(null) + + findViewById(android.R.id.content).setOnClickListener(null) + scenes.set(scenesLength, 1722916383) scenesLength++ @@ -2607,7 +2902,7 @@ class MainActivity : Activity() { var saves = inputStream.bufferedReader().use { it.readText() } inputStream.close() - val newSave = "{\"scene\":1722916384,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + "}" + val newSave = "{\"scene\":1722916384,\"scenario\":\"background_thanking\",\"characters\":[{\"name\":\"Pedro\"}],\"history\":" + scenesToJson() + ",\"items\":" + itemsToJson() + "}" if (saves == "[]") saves = "[" + newSave + "]" else saves = saves.dropLast(1) + "," + newSave + "]" @@ -2636,6 +2931,16 @@ class MainActivity : Activity() { buttonMenu.layoutParams = layoutParamsMenu buttonMenu.setOnClickListener { + for (j in 0 until scenesLength) { + scenes.set(j, 0) + } + scenesLength = 0 + + for (j in 0 until itemsLength) { + items.set(j, 0) + } + itemsLength = 0 + handler.removeCallbacksAndMessages(null) findViewById(android.R.id.content).setOnClickListener(null) @@ -2685,6 +2990,10 @@ class MainActivity : Activity() { frameLayout.addView(buttonBack) findViewById(android.R.id.content).setOnClickListener { + handler.removeCallbacksAndMessages(null) + + findViewById(android.R.id.content).setOnClickListener(null) + scenes.set(scenesLength, 1722916384) scenesLength++ @@ -2699,6 +3008,7 @@ class MainActivity : Activity() { 1722916382 -> scene1() 1722916385 -> scene4(false) 1722916386 -> scene5() + 969329308 -> no_items() 1722916383 -> scene2(false) 1722916384 -> scene3(false) } @@ -2725,6 +3035,16 @@ class MainActivity : Activity() { outputStream.close() } + private fun itemsToJson(): String { + var json = "[" + + for (i in 0 until itemsLength) { + json += items.get(i).toString() + "," + } + + return json.dropLast(1) + "]" + } + private fun scenesToJson(): String { var json = "[" diff --git a/docs/coder/init.md b/docs/coder/init.md index 581f85a..997412e 100644 --- a/docs/coder/init.md +++ b/docs/coder/init.md @@ -18,6 +18,7 @@ coder.init({ hashAchievementIds: true, hashScenesNames: true, reuseResources: true, + hashItemIds: true, minify: true } }) @@ -35,6 +36,7 @@ coder.init({ - `hashScenesNames`: If `true`, the code generator, in code generation time, will hash the names of the scenes in `saves` switch page to reduce overhead of checking strings. - `hashAchievementIds`: If `true`, the code generator, in code generation time, will hash the IDs of the achievements in `achievements` switch page to reduce overhead of checking strings. - `reuseResources`: If `true`, the code generator will reuse any `sdp` and `ssp` resources that are identical in each scene. (low - speed & code size) + - `hashItemIds`: If `true`, the code generator, in code generation time, will hash the IDs of the items in `items` switch page to reduce overhead of checking strings. - `minify`: If `true`, the code generator will minify the generated code by removing the identation spaces. (low - code size) ## Return value diff --git a/docs/items/give.md b/docs/items/give.md new file mode 100644 index 0000000..ac9441b --- /dev/null +++ b/docs/items/give.md @@ -0,0 +1,30 @@ +# Items - Give + +## Description + +Sets to give the player an item when the player reaches the scene. + +## Syntax + +```js +perfor.achievements.give(scene, 'first_item') + +``` + +## Parameters + +- `scene`: The scene configurations from the `init` function. +- `id`: The unique ID of the item to give. + +## Return value + +This function will return the scene configurations if successful, otherwise, it will execute `new Error` to terminate the generation process. + +## Platform support + +- [x] Android +- [ ] iOS +- [ ] Windows +- [ ] Linux distros +- [ ] MacOS +- [ ] Web diff --git a/docs/items/init.md b/docs/items/init.md new file mode 100644 index 0000000..d77378c --- /dev/null +++ b/docs/items/init.md @@ -0,0 +1,33 @@ +# Items - Init + +## Description + +Initializes the basic variables with information of the items for the functionality of the items core, **necessary** for the use of any items function. + +## Syntax + +```js +perfor.items.init([{ + id: 'first_item', + name: 'First item' +}]) +``` + +## Parameters + +- `options`: The options of the item. + - `id`: The unique ID of the item. + - `name`: The name of the item. + +## Return value + +This function will return `undefined` if the initialization was successful, otherwise, it will execute `new Error` to terminate the generation process. + +## Platform support + +- [x] Android +- [ ] iOS +- [ ] Windows +- [ ] Linux distros +- [ ] MacOS +- [ ] Web diff --git a/docs/scene/addSubScenes.md b/docs/scene/addSubScenes.md index 2001c27..5c36bc2 100644 --- a/docs/scene/addSubScenes.md +++ b/docs/scene/addSubScenes.md @@ -7,7 +7,18 @@ Adds a fade in transition to the scenes when entering the scene. ## Syntax ```js -scene.addSubScenes(scene, [{ text: 'second', scene: 'scene2' }, { text: 'third', scene: 'scene3' }]) +scene.addSubScenes(scene, [{ + text: 'second', + scene: 'scene2', + item: { + require: 'first_item' + remove: true + } + }, { + text: 'third', + scene: 'scene3' + } +]) ``` OBS: Maximum of 2 sub-scenes, if more are added, PerforVNM will ignore the rest. @@ -16,13 +27,15 @@ OBS: Maximum of 2 sub-scenes, if more are added, PerforVNM will ignore the rest. - `scene`: The scene configurations from the `init` function. - `options`: The options of the scenario. An object with the following property: - - `subScenes`: The sub-scenes configurations. + - `subScenes`: The array of sub-scenes configurations (2) - `subScene` is an array of objects with the following properties: - `text`: The text that will be displayed in the sub-scene button. - `scene`: The name of the scene that will be called when the sub-scene button is pressed. - - `subScene` is an array of objects with the following properties (2nd sub-scene): - - `text`: The text that will be displayed in the sub-scene button. - - `scene`: The name of the scene that will be called when the sub-scene button is pressed. + - `item`: The item configurations. An object with the following properties: + - `require`: The item that is required to go to the next scene. An object with the following properties: + - `id`: The unique ID of the item. + - `fallback`: The scene that will be called if the player does not have the item. + - `remove`: If the item will be removed from the player's inventory. ## Return value diff --git a/docs/scene/setNextScene.md b/docs/scene/setNextScene.md index cd0c084..cda4f6f 100644 --- a/docs/scene/setNextScene.md +++ b/docs/scene/setNextScene.md @@ -7,13 +7,28 @@ Adds a fade in transition to the scenes when entering the scene. ## Syntax ```js -scene.setNextScene(scene, 'scene4') +scene.setNextScene(scene, { + scene: 'scene4', + item: { + require: { + id: 'first_item', + fallback: 'no_items' + }, + remove: true + } +}) ``` ## Parameters - `scene`: The scene configurations from the `init` function. -- `nextScene`: The name of the next scene. +- `options`: The options of the scenario. An object with the following property: + - `scene`: The name of the scene that will be called when the scene ends. + - `item`: The item configurations. An object with the following properties: + - `require`: The item that is required to go to the next scene. An object with the following properties: + - `id`: The unique ID of the item. + - `fallback`: The scene that will be called if the player does not have the item. + - `remove`: If the item will be removed from the player's inventory. ## Return value diff --git a/index.js b/index.js index 60fa8a4..1b4a388 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ import subScene from './src/sub-scene.js' import menu from './src/menu.js' import achievements from './src/achievements.js' import custom from './src/custom.js' +import items from './src/items.js' export default { coder, @@ -11,5 +12,6 @@ export default { subScene, menu, achievements, - custom + custom, + items } \ No newline at end of file diff --git a/package.json b/package.json index 85ecb23..1e97dcf 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "doc": "docs" }, "scripts": { - "test": "bun run tests/vn.js" + "test": "node tests/vn.js" }, "repository": { "type": "git", diff --git a/src/achievements.js b/src/achievements.js index 64dc2d7..3ad4604 100644 --- a/src/achievements.js +++ b/src/achievements.js @@ -1,3 +1,6 @@ +/* TODO: Achievements searchs from O(n) to O(1) through objects */ +/* TODO: Option for scenes to require an achievement and if not, fallback to another scene */ + import helper from './helper.js' function init(options) { diff --git a/src/coder.js b/src/coder.js index bb5dde0..f6a7dbb 100644 --- a/src/coder.js +++ b/src/coder.js @@ -1,10 +1,13 @@ +/* TODO: Remove ability to use achievements, items and menu init functions after using scene.finalize */ + import fs from 'fs' import helper from './helper.js' import achievements from './achievements.js' +import { _ItemsParsingFunction, _ItemsRestore, _ItemsSaver } from './items.js' -global.visualNovel = { menu: null, info: null, internalInfo: {}, code: '', scenes: [], subScenes: [], achievements: [], customXML: [], optimizations: {} } +global.visualNovel = { menu: null, info: null, internalInfo: {}, code: '', scenes: [], subScenes: [], achievements: [], items: [], customXML: [], optimizations: {} } global.PerforVNM = { codeGeneratorVersion: '1.23.0', generatedCodeVersion: '1.21.0', @@ -50,6 +53,10 @@ function init(options) { type: 'boolean', required: false }, + 'hashItemsId': { + type: 'boolean', + required: false + }, 'preCalculateRounding': { type: 'boolean', required: false @@ -73,6 +80,7 @@ function init(options) { visualNovel.info = options + /* TODO: Only add necessary `import`s */ visualNovel.code = helper.codePrepare(` package ${options.applicationId} @@ -87,6 +95,7 @@ function init(options) { import android.app.Activity import android.util.TypedValue import android.media.MediaPlayer + import android.widget.Toast import android.widget.TextView import android.widget.ImageView import android.widget.ScrollView @@ -162,6 +171,7 @@ function init(options) { helper.logOk('Base configuration files and main function coded.', 'Android') } +/* TODO: Create functions for stopping the music and sound effects */ function finalize() { helper.replace('__PERFORVNM_CODE__', '') @@ -180,43 +190,57 @@ function finalize() { visualNovel.scenes.forEach((scene, i) => { savesSwitchCode += helper.sceneEach(scene) - if (i != visualNovel.scenes.length - 1) { - const nextScene = visualNovel.scenes[i + 1] + if (scene.next) { + const nextScene = visualNovel.scenes.find((nScene) => nScene.name == scene.next.scene) const finishScene = [] - if (scene.effect || scene.music) { - finishScene.push( - helper.codePrepare(` - if (mediaPlayer != null) { - mediaPlayer!!.stop() - mediaPlayer!!.release() - mediaPlayer = null - }\n`, 14 - ) - ) - } - - if (scene.effect && scene.music) { - finishScene.push( - helper.codePrepare(` - if (mediaPlayer2 != null) { - mediaPlayer2!!.stop() - mediaPlayer2!!.release() - mediaPlayer2 = null - }\n`, 14 - ) - ) - } - - if (scene.speech || (scene.effect && scene.effect.delay != 0) || (scene.music && scene.music.delay != 0)) - finishScene.push(helper.codePrepare('handler.removeCallbacksAndMessages(null)', 0, 14, false)) + if (scene.next.item?.require?.fallback) { + const fallbackScene = visualNovel.scenes.find((fScene) => fScene.name == scene.next.item.require.fallback) + const functionParams = [] - if (i == visualNovel.scenes.length - 2) - finishScene.push(helper.codePrepare('findViewById(android.R.id.content).setOnClickListener(null)', 0, 14, false)) + if (fallbackScene.speech && !scene.speech) { + functionParams.push('true') + } + if (fallbackScene.speech?.author?.name && scene.speech && !scene.speech?.author?.name) { + functionParams.push('true') + } - finishScene.push(helper.codePrepare('it.setOnClickListener(null)', 0, 14, false)) + scene.code = scene.code.replace('__PERFORVNM_FALLBACK_SCENE_PARAMS__', functionParams.join(', ')) + } if (scene.subScenes.length == 0) { + if (scene.effect || scene.music) { + finishScene.push( + helper.codePrepare(` + if (mediaPlayer != null) { + mediaPlayer!!.stop() + mediaPlayer!!.release() + mediaPlayer = null + }\n`, 14 + ) + ) + } + + if (scene.effect && scene.music) { + finishScene.push( + helper.codePrepare(` + if (mediaPlayer2 != null) { + mediaPlayer2!!.stop() + mediaPlayer2!!.release() + mediaPlayer2 = null + }\n`, 14 + ) + ) + } + + if (scene.speech || (scene.effect && scene.effect.delay != 0) || (scene.music && scene.music.delay != 0)) + finishScene.push(helper.codePrepare('handler.removeCallbacksAndMessages(null)', 0, 14, false)) + + if (i == visualNovel.scenes.length - 2) + finishScene.push(helper.codePrepare('findViewById(android.R.id.content).setOnClickListener(null)', 0, 14, false)) + + finishScene.push(helper.codePrepare('it.setOnClickListener(null)', 0, 14, false)) + let code = '\n\n' + helper.codePrepare(` findViewById(android.R.id.content).setOnClickListener { ${finishScene.join('\n')}\n\n`, 8, 0) @@ -321,9 +345,18 @@ ${finishScene.join('\n')}\n\n`, 8, 0) functionParams2.switch.push('true') } + const functionParams = [] + if (scene.speech && !nextScene.speech) { + functionParams.push('true') + } + if (scene.speech?.author?.name && nextScene.speech && !nextScene.speech?.author?.name) { + functionParams.push('true') + } + switchesCode += '\n' + helper.codePrepare(`${helper.getSceneId(scene.name)} -> ${scene.name}(${functionParams2.switch.join(', ').replace(/true/g, 'false')})`, 0, 6, false) scene.code = scene.code.replace('__PERFORVNM_SCENE_PARAMS__', functionParams2.function.join(', ')) + scene.code = scene.code.replace('__PERFORVNM_NEXT_SCENE_PARAMS__', functionParams.join(', ')) if (scene.speech) { const speechHandler = helper.codePrepare(` @@ -419,8 +452,8 @@ ${finishScene.join('\n')}\n\n`, 8, 0) visualNovel.subScenes.forEach((scene, i) => { savesSwitchCode += helper.sceneEach(scene) - if (scene.next) { - const nextScene = visualNovel.scenes.find((nScene) => nScene.name == scene.next) + if (scene.next.scene) { + const nextScene = visualNovel.scenes.find((nScene) => nScene.name == scene.next.scene) if (!nextScene) helper.logFatal('Next scene does not exist.') @@ -606,6 +639,16 @@ ${finishScene.join('\n')}\n\n`, 6, 0) if (visualNovel.achievements.length != 0) helper.writeFunction(achievements._AchievementGiveFunction()) + if (visualNovel.items.length != 0) { + helper.writeFunction(_ItemsParsingFunction()) + + helper.replace('__PERFORVNM_ITEMS_RESTORE__', _ItemsRestore()) + helper.replace(/__PERFORVNM_ITEMS_SAVER__/g, _ItemsSaver()) + } else { + helper.replace('__PERFORVNM_ITEMS_RESTORE__', '') + helper.replace(/__PERFORVNM_ITEMS_SAVER__/g, '') + } + helper.replace('__PERFORVNM_SCENES__', '') helper.replace('__PERFORVNM_MENU__', '// No menu created.') helper.replace('__PERFORVNM_CLASSES__', '') @@ -686,11 +729,18 @@ ${finishScene.join('\n')}\n\n`, 6, 0) helper.replace(/__PERFORVNM_RELEASE_MEDIA_PLAYER__/g, releaseCode) } + helper.replace(/__PERFORVNM_SCENES_LENGTH__/g, visualNovel.scenes.length + visualNovel.subScenes.length) + let addHeaders = helper.codePrepare(` private var scenes = MutableList<${visualNovel.optimizations.hashScenesNames ? 'Int' : 'String'}>(${visualNovel.scenes.length + visualNovel.subScenes.length}) { ${visualNovel.optimizations.hashScenesNames ? '0' : '""'} } private var scenesLength = 1\n`, 2 ) + if (visualNovel.items.length != 0) + addHeaders += helper.codePrepare(` + private var items = MutableList<${visualNovel.optimizations.hashItemsId ? 'Int' : 'String'}>(${visualNovel.items.length}) { ${visualNovel.optimizations.hashItemsId ? '0' : '""'} } + private var itemsLength = 0\n`, 4) + if (visualNovel.internalInfo.hasSpeech || visualNovel.internalInfo.hasDelayedSoundEffect || visualNovel.internalInfo.hasEffect || visualNovel.internalInfo.hasDelayedMusic || visualNovel.internalInfo.hasDelayedAnimation) addHeaders += helper.codePrepare('private val handler = Handler(Looper.getMainLooper())\n', 0, 2, false) diff --git a/src/helper.js b/src/helper.js index b3bc2c2..590647f 100644 --- a/src/helper.js +++ b/src/helper.js @@ -1,3 +1,6 @@ +/* TODO: Create helper folder and move functions to there */ +/* CRITICAL TODO: Avoid collisions in hash functions */ + import fs from 'fs' function writeFunction(sceneCode) { @@ -323,6 +326,11 @@ function getAchievementId(achievement, parsed) { } } +function getItemId(item) { + if (visualNovel.optimizations.hashItemsId) return hash(item) + else return `"${item}"` +} + function removeAllDoubleLines(code) { switch (process.platform) { case 'win32': @@ -334,21 +342,21 @@ function removeAllDoubleLines(code) { function sceneEach(scene) { - let savesSwitchLocal = helper.codePrepare(` - ${helper.getSceneId(scene.name)} -> { + let savesSwitchLocal = codePrepare(` + ${getSceneId(scene.name)} -> { when (characterData.getString("name")) {`, 0, 6, false ) scene.characters.forEach((character) => { let optimizedSetImage = '' if (visualNovel.optimizations.preCalculateScenesInfo) { - optimizedSetImage = helper.codePrepare(` + optimizedSetImage = codePrepare(` imageViewCharacter.setImageResource(R.raw.${character.image})\n\n `, 8 ) } switch (character.position.side) { case 'left': { - savesSwitchLocal += helper.codePrepare(` + savesSwitchLocal += codePrepare(` "${character.name}" -> { ${optimizedSetImage}val leftDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._${Math.round(character.position.margins.side * 0.25)}sdp) @@ -359,7 +367,7 @@ function sceneEach(scene) { break } case 'leftTop': { - savesSwitchLocal += helper.codePrepare(` + savesSwitchLocal += codePrepare(` "${character.name}" -> { ${optimizedSetImage}val leftDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._${Math.round(character.position.margins.side * 0.25)}sdp) val topDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._${Math.round(character.position.margins.top * 0.25)}sdp) @@ -371,7 +379,7 @@ function sceneEach(scene) { break } case 'right': { - savesSwitchLocal += helper.codePrepare(` + savesSwitchLocal += codePrepare(` "${character.name}" -> { ${optimizedSetImage}val rightDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._${Math.round(character.position.margins.side * 0.25)}sdp) @@ -382,7 +390,7 @@ function sceneEach(scene) { break } case 'rightTop': { - savesSwitchLocal += helper.codePrepare(` + savesSwitchLocal += codePrepare(` "${character.name}" -> { ${optimizedSetImage}val rightDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._${Math.round(character.position.margins.side * 0.25)}sdp) val topDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._${Math.round(character.position.margins.top * 0.25)}sdp) @@ -394,7 +402,7 @@ function sceneEach(scene) { break } case 'top': { - savesSwitchLocal += helper.codePrepare(` + savesSwitchLocal += codePrepare(` "${character.name}" -> { ${optimizedSetImage}val topDpCharacter = resources.getDimensionPixelSize(com.intuit.sdp.R.dimen._${Math.round(character.position.margins.side * 0.25)}sdp) @@ -405,7 +413,7 @@ function sceneEach(scene) { break } case 'center': { - savesSwitchLocal += helper.codePrepare(` + savesSwitchLocal += codePrepare(` "${character.name}" -> { ${optimizedSetImage}layoutParamsImageViewCharacter.setMargins(leftDpLoad, topDpLoad, 0, 0) }`, 0, 4, false @@ -416,14 +424,14 @@ function sceneEach(scene) { } }) - return savesSwitchLocal + '\n' + helper.codePrepare('}\n', 0, 12, false) + helper.codePrepare('}', 0, 10, false) + return savesSwitchLocal + '\n' + codePrepare('}\n', 0, 12, false) + codePrepare('}', 0, 10, false) } function sceneEachFinalize(savesSwitchCode) { - return helper.codePrepare(` + return codePrepare(` when (buttonData.get${visualNovel.optimizations.hashScenesNames ? 'Int' : 'String'}("scene")) {`, 0, 4 ) + savesSwitchCode + '\n' + - helper.codePrepare('}', 0, 8, false) + codePrepare('}', 0, 8, false) } export default { @@ -444,6 +452,7 @@ export default { hash, getSceneId, getAchievementId, + getItemId, removeAllDoubleLines, sceneEach, sceneEachFinalize diff --git a/src/items.js b/src/items.js new file mode 100644 index 0000000..3121db4 --- /dev/null +++ b/src/items.js @@ -0,0 +1,90 @@ +/* TODO: Items searchs from O(n) to O(1) through objects */ +/* TODO: Cross-saves items */ +/* TODO: Option for scenes to require an item and if not, fallback to another scene */ + +import helper from './helper.js' + +export function init(options) { + const checks = { + 'id': { + type: 'string', + extraVerification: (param) => { + if (visualNovel.items.find((achievement) => achievement.id == param)) + helper.logFatal('An item already exists with this id.') + } + }, + 'name': { + type: 'string', + extraVerification: (param) => { + if (visualNovel.items.find((item) => item.name == param)) + helper.logFatal('An item already exists with this name.') + } + } + } + + helper.verifyParams(checks, options) + + visualNovel.items = options || [] +} + +export function give(page, itemId) { + const checks = { + 'id': { + type: 'string', + extraVerification: (param) => { + if (!visualNovel.items.find((item) => item.id == param)) + helper.logFatal(`The item '${param}' doesn't exist.`) + + if (page.items.give.find((item) => item.id == param)) + helper.logFatal(`The item '${param}' was already given.`) + } + } + } + + page.items.give.push(itemId) + + return page +} + +export function _ItemGive(item) { + return helper.codePrepare(` + items.set(itemsLength, ${helper.getItemId(item)}) + itemsLength++\n\n`) +} + +export function _ItemRemove(item) { + return helper.codePrepare(` + items.set(itemsLength, ${visualNovel.optimizations.hashItemsId ? '0' : '""'}) + itemsLength--`, 0, 6) +} + +export function _ItemsParsingFunction() { + return helper.codePrepare(` + private fun itemsToJson(): String { + var json = "[" + + for (i in 0 until itemsLength) { + json += ${visualNovel.optimizations.hashItemsId ? 'items.get(i).toString() + "' : '"\\"" + items.get(i) + "\\"'}," + } + + return json.dropLast(1) + "]" + }`, 2) +} + +export function _ItemsRestore() { + return helper.codePrepare(`\n + val sceneItems = buttonData.getJSONArray("items") + for (j in 0 until sceneItems.length()) { + items.set(j, sceneItems.getInt(j)) + } + itemsLength = sceneItems.length()`, 0, 4, false) +} + +export function _ItemsSaver() { + return helper.codePrepare(` ",\\"items\\":" + itemsToJson() + `, 0, 0, false) +} + +export default { + init, + give +} \ No newline at end of file diff --git a/src/menu.js b/src/menu.js index 043bde9..fccf462 100644 --- a/src/menu.js +++ b/src/menu.js @@ -1,3 +1,6 @@ +/* TODO: Allow to remove footer and add buttons manually */ +/* TODO (Critical): Fix saves, if saves topDp > 620 then it will crash the app */ + import helper from './helper.js' function init(options) { @@ -94,6 +97,7 @@ function init(options) { } function finalize(menu) { + /* TODO: Centralized private function that will generate custom code */ let customCode = '' menu.custom.forEach((custom, index) => { switch (custom.type) { @@ -1845,7 +1849,7 @@ __PERFORVNM_SAVES_SWITCH__ for (j in 0 until historyScenes.length()) { scenes.set(j, historyScenes.get${visualNovel.optimizations.hashScenesNames ? 'Int' : 'String'}(j)) } - scenesLength = historyScenes.length() + scenesLength = historyScenes.length()__PERFORVNM_ITEMS_RESTORE__ switchScene(buttonData.${visualNovel.optimizations.hashScenesNames ? 'getInt' : 'getString'}("scene")) } diff --git a/src/scene.js b/src/scene.js index a5ec007..7237b42 100644 --- a/src/scene.js +++ b/src/scene.js @@ -1,5 +1,10 @@ +/* TODO: Scenes searchs from O(n) to O(1) through objects */ +/* TODO (unconfirmed): Set the scenes in order through a queue [ 'scene1', 'scene2', ... ] */ + import helper from './helper.js' +import { _ItemGive, _ItemRemove } from './items.js' + function init(options) { const checks = { 'name': { @@ -44,6 +49,10 @@ function init(options) { music: null, transition: null, achievements: [], + items: { + give: [], + require: [] + }, custom: [], resources: [] } @@ -299,12 +308,41 @@ function setNextScene(scene, options) { const checks = { 'scene': { type: 'string' + }, + 'item': { + type: 'object', + required: false, + params: { + 'require': { + type: 'object', + params: { + 'id': { + type: 'string', + extraVerification: (param) => { + if (!visualNovel.items.find((item) => item.id == param)) + helper.logFatal(`The item '${param}' doesn't exist.`) + } + }, + 'fallback': { + type: 'string', + } + } + }, + 'remove': { + type: 'boolean', + required: false, + extraVerification: (param, additionalinfo) => { + if (param && !additionalinfo.parent?.require?.id) + helper.logFatal('You must specify an item to be removed once used.') + } + } + } } } helper.verifyParams(checks, options) - scene.next = options.scene + scene.next = options return scene } @@ -314,6 +352,27 @@ function addSubScenes(scene, options) { 'text': { type: 'string' }, + 'item': { + type: 'object', + required: false, + params: { + 'require': { + type: 'string', + extraVerification: (param) => { + if (!visualNovel.items.find((item) => item.id == param)) + helper.logFatal(`The item '${param}' doesn't exist.`) + } + }, + 'remove': { + type: 'boolean', + required: false, + extraVerification: (param, additionalinfo) => { + if (param && !additionalinfo.parent?.item?.require) + helper.logFatal('You must specify an item to be removed once used.') + } + } + } + }, 'scene': { type: 'string', extraVerification: (param) => { @@ -822,7 +881,7 @@ function finalize(scene) { rectangleViewSpeech.layoutParams = layoutParamsRectangleSpeech\n`, 2, 0, false ) - const oldScene = visualNovel.subScenes.find((subScene) => subScene.next == scene.name) || visualNovel.scenes[visualNovel.scenes.length - 1] + const oldScene = visualNovel.subScenes.find((subScene) => subScene.next.scene == scene.name) || visualNovel.scenes[visualNovel.scenes.length - 1] if (visualNovel.scenes.length != 0 && oldScene.speech) { sceneCode += helper.codePrepare(`rectangleViewSpeech.setAlpha(${scene.speech.text.rectangle.opacity}f)\n`, 0, 4, false) @@ -930,7 +989,7 @@ function finalize(scene) { textViewAuthor.layoutParams = layoutParamsAuthor\n\n`, 4, 0, false ) - const oldScene = visualNovel.subScenes.find((subScene) => subScene.next == scene.name) || visualNovel.scenes[visualNovel.scenes.length - 1] + const oldScene = visualNovel.subScenes.find((subScene) => subScene.next.scene == scene.name) || visualNovel.scenes[visualNovel.scenes.length - 1] if ( visualNovel.scenes.length == 0 || @@ -1074,7 +1133,7 @@ function finalize(scene) { mediaPlayer!!.stop() mediaPlayer!!.release() mediaPlayer = null - }` + }`, 0, 2 ) ) } @@ -1086,7 +1145,7 @@ function finalize(scene) { mediaPlayer!!.stop() mediaPlayer!!.release() mediaPlayer = null - }` + }`, 0, 2 ) ) @@ -1096,15 +1155,22 @@ function finalize(scene) { mediaPlayer2!!.stop() mediaPlayer2!!.release() mediaPlayer2 = null - }` + }`, 0, 2 ) ) } if (scene.speech || (scene.effect && scene.effect.delay != 0) || (scene.music && scene.music.delay != 0)) - finishScene.push(helper.codePrepare('handler.removeCallbacksAndMessages(null)', 0, 8, false)) + finishScene.push(helper.codePrepare('handler.removeCallbacksAndMessages(null)', 0, 10, false)) - finishScene.push(helper.codePrepare('findViewById(android.R.id.content).setOnClickListener(null)', 0, 8, false)) + finishScene.push(helper.codePrepare('findViewById(android.R.id.content).setOnClickListener(null)', 0, 10, false)) + + const itemRemover = [] + if (scene.items.give.length != 0) { + scene.items.give.forEach((item) => { + itemRemover.push(_ItemRemove(item)) + }) + } const buttonSizeSsp = helper.getResource(scene, { type: 'ssp', dp: '8' }) scene = helper.addResource(scene, { type: 'ssp', dp: '8', spaces: 4 }) @@ -1180,7 +1246,7 @@ function finalize(scene) { scene = helper.addResource(scene, { type: 'sdp', dp: '23', spaces: 4 }) sceneCode += helper.codePrepare(` - val newSave = "${JSON.stringify(JSON.stringify(sceneJson)).slice(1, -2)},\\"history\\":" + scenesToJson() + "}" + val newSave = "${JSON.stringify(JSON.stringify(sceneJson)).slice(1, -2)},\\"history\\":" + scenesToJson() +__PERFORVNM_ITEMS_SAVER__ "}" if (saves == "[]") saves = "[" + newSave + "]" else saves = saves.dropLast(1) + "," + newSave + "]" @@ -1209,9 +1275,23 @@ function finalize(scene) { buttonMenu.layoutParams = layoutParamsMenu\n\n` ) + let itemsRemove = '' + if (visualNovel.items.length != 0) { + itemsRemove = helper.codePrepare(`\n + for (j in 0 until itemsLength) { + items.set(j, ${visualNovel.optimizations.hashItemsId ? '0' : '""'}) + } + itemsLength = 0`, 0, 4, false + ) + } sceneCode += helper.codePrepare(` - buttonMenu.setOnClickListener { -${finishScene.join('\n\n')}__PERFORVNM_START_MUSIC__\n\n`, 2 + buttonMenu.setOnClickListener { + for (j in 0 until scenesLength) { + scenes.set(j, ${visualNovel.optimizations.hashScenesNames ? '0' : '""'}) + } + scenesLength = 0${itemsRemove} + +${finishScene.join('\n\n')}__PERFORVNM_START_MUSIC__\n\n`, 4 ) sceneCode += helper.codePrepare(` @@ -1244,13 +1324,13 @@ ${finishScene.join('\n\n')}__PERFORVNM_START_MUSIC__\n\n`, 2 ) sceneCode += helper.codePrepare(` - buttonBack.setOnClickListener { -${finishScene.join('\n\n')}\n\n`, 2 + buttonBack.setOnClickListener { +${finishScene.join('\n\n')}${itemRemover.join('\n\n')}\n\n`, 4 ) - const oldScene = visualNovel.subScenes.find((subScene) => subScene.next == scene.name) || visualNovel.scenes[visualNovel.scenes.length - 1] + let oldScene = visualNovel.subScenes.find((subScene) => subScene.next.scene == scene.name) - if (visualNovel.subScenes.find((subScene) => subScene.next == scene.name)) { + if (oldScene || visualNovel.scenes.find((cScene) => scene.name == cScene.next?.item?.require?.fallback) || visualNovel.subScenes.find((cScene) => scene.name == cScene.next?.item?.require?.fallback)) { sceneCode += helper.codePrepare(` val scene = scenes.get(scenesLength - 1) @@ -1260,14 +1340,24 @@ ${finishScene.join('\n\n')}\n\n`, 2 switchScene(scene)\n`, 2 ) } else { + oldScene = visualNovel.scenes[visualNovel.scenes.length - 1] + + const functionParams = [] + const olderOldScene = visualNovel.subScenes.find((subScene) => subScene.next.scene == oldScene.name) + + if (olderOldScene) { + if (olderOldScene.speech && oldScene.speech) functionParams.push('false') + if (olderOldScene.speech?.author?.name && olderOldScene.speech && !olderOldScene.speech?.author?.name) functionParams.push('false') + } + if (visualNovel.scenes.length == 1) { - sceneCode += helper.codePrepare(`${oldScene.name}(${(oldScene.speech ? 'false' : '' )})\n`, 0, 6, false) + sceneCode += helper.codePrepare(`${oldScene.name}(${functionParams.join(', ')})\n`, 0, 6, false) } else { sceneCode += helper.codePrepare(` scenesLength-- scenes.set(scenesLength, ${visualNovel.optimizations.hashScenesNames ? '0' : '""'}) - ${oldScene.name}(${(oldScene.speech ? 'false' : '' )})\n`, 4 + ${oldScene.name}(${functionParams.join(', ')})\n`, 4 ) } } @@ -1311,43 +1401,72 @@ ${finishScene.join('\n\n')}\n\n`, 2 const sdp150 = helper.getResource(scene, { type: 'sdp', dp: '150' }) scene = helper.addResource(scene, { type: 'sdp', dp: '150', spaces: 4 }) - sceneCode += helper.codePrepare(` - buttonSubScenes.setOnClickListener { -${finishScene.join('\n\n')} + let requireItems = [ '', '' ] + let i = 0 + + while (true) { + if (scene.subScenes[i].item?.require) { + requireItems[i] = helper.codePrepare(` + if (!items.contains(${helper.getItemId(scene.subScenes[0].item.require)})) { + Toast.makeText(this, "You don't have the required item.", Toast.LENGTH_SHORT).show()`) + + if (scene.subScenes[i].item.remove) { + requireItems[i] += helper.codePrepare(` + items.remove(${helper.getItemId(scene.subScenes[0].item.require)}) - __PERFORVNM_SUBSCENE_1__ + return@setOnClickListener + }\n\n`, 2, 0, false) + } else { + requireItems[i] += helper.codePrepare(`\n + return@setOnClickListener + }\n\n`, 2, 0, false) + } } - frameLayout.addView(buttonSubScenes) + if (i == 1) break - val buttonSubScenes2 = Button(this) - buttonSubScenes2.text = "${scene.subScenes[1].text}" - buttonSubScenes2.setTextSize(TypedValue.COMPLEX_UNIT_PX, ${subScenesTextSsp.variable}) - buttonSubScenes2.setTextColor(0xFF${scene.options.buttonsColor}.toInt()) - buttonSubScenes2.background = null + i++ + } - val layoutParamsSubScenes2 = LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT - ) + sceneCode += helper.codePrepare(` + buttonSubScenes.setOnClickListener { +${requireItems[0]}${finishScene.join('\n\n')} - ${sdp150.definition}layoutParamsSubScenes2.gravity = Gravity.CENTER_HORIZONTAL - layoutParamsSubScenes2.setMargins(0, ${sdp150.variable}, 0, 0) + __PERFORVNM_SUBSCENE_1__ + } - buttonSubScenes2.layoutParams = layoutParamsSubScenes2 + frameLayout.addView(buttonSubScenes) - buttonSubScenes2.setOnClickListener { -${finishScene.join('\n\n')} + val buttonSubScenes2 = Button(this) + buttonSubScenes2.text = "${scene.subScenes[1].text}" + buttonSubScenes2.setTextSize(TypedValue.COMPLEX_UNIT_PX, ${subScenesTextSsp.variable}) + buttonSubScenes2.setTextColor(0xFF${scene.options.buttonsColor}.toInt()) + buttonSubScenes2.background = null - __PERFORVNM_SUBSCENE_2__ - } + val layoutParamsSubScenes2 = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + + ${sdp150.definition}layoutParamsSubScenes2.gravity = Gravity.CENTER_HORIZONTAL + layoutParamsSubScenes2.setMargins(0, ${sdp150.variable}, 0, 0) + + buttonSubScenes2.layoutParams = layoutParamsSubScenes2 - frameLayout.addView(buttonSubScenes2)\n\n`, 2 + buttonSubScenes2.setOnClickListener { +${requireItems[1]}${finishScene.join('\n\n')} + + __PERFORVNM_SUBSCENE_2__ + } + + frameLayout.addView(buttonSubScenes2)\n\n`, 4 ) } else if (scene.subScenes.length != 0) { + /* TODO: Allow to have more than 2 sub-scenes */ helper.logWarning('Unecessary sub-scenes, only 2 are allowed.', 'Android') } + /* TODO: Centralized private function that will generate custom code */ scene.custom.forEach((custom, index) => { switch (custom.type) { case 'text': { @@ -1730,32 +1849,39 @@ ${finishScene.join('\n\n')} else return `giveAchievement(${helper.getAchievementId(achievement.id)}, "${helper.getAchievementId(achievement.id, true)}")` }).join('\n')}\n\n`, 2) - } + } /* TODO: Move this to private function of achievements.js */ + + scene.items.give.forEach((item) => sceneCode += _ItemGive(item)) - if (scene.type == 'normal') { + if (scene.next?.scene) { + let nextCode = helper.codePrepare(`${scene.next.scene}(__PERFORVNM_NEXT_SCENE_PARAMS__)`, 0, 10, false) + + if (scene.next.item?.require) { + nextCode = helper.codePrepare(` + if (!items.contains(${helper.getItemId(scene.next.item.require.id)})) { + ${scene.next.item.require.fallback}(__PERFORVNM_FALLBACK_SCENE_PARAMS__) + } else { + ${scene.next.scene}(__PERFORVNM_NEXT_SCENE_PARAMS__) + }`, 0, 2) + } sceneCode += helper.codePrepare(` - setContentView(frameLayout)__PERFORVNM_SCENE_${scene.name.toUpperCase()}__ + findViewById(android.R.id.content).setOnClickListener { +${finishScene.join('\n\n')} + + scenes.set(scenesLength, ${helper.getSceneId(scene.name)}) + scenesLength++ + +${nextCode} + } + + setContentView(frameLayout) }`, 4 ) } else { - if (scene.next) { - sceneCode += helper.codePrepare(` - findViewById(android.R.id.content).setOnClickListener { - scenes.set(scenesLength, ${helper.getSceneId(scene.name)}) - scenesLength++ - - ${scene.next}(__PERFORVNM_NEXT_SCENE_PARAMS__) - } - - setContentView(frameLayout) - }`, 6 - ) - } else { - sceneCode += helper.codePrepare(` - setContentView(frameLayout) - }`, 6 - ) - } + sceneCode += helper.codePrepare(` + setContentView(frameLayout) + }`, 4 + ) } sceneCode = helper.finalizeResources(scene, sceneCode) diff --git a/src/sub-scene.js b/src/sub-scene.js index b40f631..52ba4d4 100644 --- a/src/sub-scene.js +++ b/src/sub-scene.js @@ -1,3 +1,5 @@ +/* TODO: Sub-scene searchs from O(n) to O(1) through objects */ + import helper from './helper.js' function init(options) { @@ -44,6 +46,10 @@ function init(options) { music: null, transition: null, achievements: [], + items: { + give: [], + require: [] + }, custom: [], resources: [] } diff --git a/tests/vn.js b/tests/vn.js index d93f3c8..20c1757 100644 --- a/tests/vn.js +++ b/tests/vn.js @@ -14,6 +14,7 @@ perfor.coder.init({ hashAchievementIds: true, hashScenesNames: true, reuseResources: true, + hashItemsId: true, // minify: true } }) @@ -92,6 +93,11 @@ perfor.achievements.init([{ image: 'achievement' }]) +perfor.items.init([{ + id: 'first_item', + name: 'First item' +}]) + let firstScene = perfor.scene.init({ name: 'scene1', textColor: 'FFFFFF', @@ -155,8 +161,20 @@ firstScene = perfor.custom.addCustomRectangle(firstScene, { firstScene = perfor.scene.addScenario(firstScene, { image: 'background_thanking' }) /* Adds a scenario to the scene */ firstScene = perfor.scene.addSoundEffects(firstScene, [{ sound: 'menu_music', delay: 0 }]) /* Adds a sound effect to the scene at second 1 */ firstScene = perfor.scene.addTransition(firstScene, { duration: 1000 }) /* Adds a transition to the scene */ -firstScene = perfor.scene.addSubScenes(firstScene, [{ text: 'second', scene: 'scene2' }, { text: 'third', scene: 'scene3' }]) /* Adds the subscenes to the first scene */ +firstScene = perfor.scene.addSubScenes(firstScene, [{ + text: 'second', + scene: 'scene2', + item: { + require: 'first_item', + remove: true + } + }, { + text: 'third', + scene: 'scene3' + } +]) /* Adds the subscenes to the first scene */ firstScene = perfor.achievements.give(firstScene, 'first_achievement') /* Gives the first achievement to the scene */ +firstScene = perfor.items.give(firstScene, 'first_item') /* Gives the first item to the scene */ perfor.scene.finalize(firstScene) /* Finishes up the scene */ let secondScene = perfor.subScene.init({ @@ -295,7 +313,16 @@ fourthScene = perfor.scene.addSpeech(fourthScene, { } } }) -fourthScene = perfor.scene.setNextScene(fourthScene, { scene: 'scene5' }) +fourthScene = perfor.scene.setNextScene(fourthScene, { + scene: 'scene5', + item: { + require: { + id: 'first_item', + fallback: 'no_items' + }, + remove: true + } +}) perfor.scene.finalize(fourthScene) let fifthScene = perfor.scene.init({ @@ -338,4 +365,44 @@ fifthScene = perfor.scene.addSpeech(fifthScene, { }) perfor.scene.finalize(fifthScene) +let noItemsScene = perfor.scene.init({ + name: 'no_items', + textColor: 'FFFFFF', + backTextColor: 'FFFFFF', + buttonsColor: 'FFFFFF', + footerTextColor: 'FFFFFF' +}) +noItemsScene = perfor.scene.addCharacter(noItemsScene, { + name: 'Pedro', + image: 'pedro_staring', + position: { + side: 'left', + margins: { + side: 20, + top: 0 + } + } +}) +noItemsScene = perfor.scene.addScenario(noItemsScene, { image: 'background_thanking' }) +noItemsScene = perfor.scene.addSpeech(noItemsScene, { + author: { + name: 'Pedro', + textColor: 'FFFFFF', + rectangle: { + color: '000000', + opacity: 0.6 + } + }, + text: { + content: '"You don\'t have the item, sorry."', + color: 'FFFFFF', + fontSize: 9, + rectangle: { + color: '000000', + opacity: 0.8 + } + } +}) +perfor.scene.finalize(noItemsScene) + perfor.coder.finalize() /* Finishes up the code */