diff --git a/CraftingRecipes/craftbot.json b/CraftingRecipes/craftbot.json
new file mode 100644
index 0000000..ea91864
--- /dev/null
+++ b/CraftingRecipes/craftbot.json
@@ -0,0 +1,2494 @@
+[
+ {
+ // "Diamond Plate Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "1f7ac0bb-ad45-4246-9817-59bdf7f7ab39",
+ "quantity": 20
+ }
+ ],
+ "itemId": "c97c17c3-81bb-48b5-9e9c-8a5117ee431a",
+ "quantity": 10
+ },
+ {
+ // "Paintable Glass Flat Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "b5ee5539-75a2-4fef-873b-ef7c9398b3f5",
+ "quantity": 20
+ }
+ ],
+ "itemId": "036ffd32-45df-11e6-beb8-9e71128cae77",
+ "quantity": 10
+ },
+ {
+ // "Glowing Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "1f7ac0bb-ad45-4246-9817-59bdf7f7ab39",
+ "quantity": 5
+ },
+ {
+ "itemId": "b5ee5539-75a2-4fef-873b-ef7c9398b3f5",
+ "quantity": 5
+ }
+ ],
+ "itemId": "e50485da-4545-4def-a585-0a5f158c6104",
+ "quantity": 10
+ },
+ {
+ // "Invisible Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "b5ee5539-75a2-4fef-873b-ef7c9398b3f5",
+ "quantity": 30
+ }
+ ],
+ "itemId": "7a73cc98-b2d4-4b28-9095-f8b9077548e4",
+ "quantity": 10
+ },
+ {
+ // "Mirror Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "b5ee5539-75a2-4fef-873b-ef7c9398b3f5",
+ "quantity": 10
+ }
+ ],
+ "itemId": "906c76ee-189a-4983-ae82-b31722e56b23",
+ "quantity": 10
+ },
+ {
+ // "Bling Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "1f7ac0bb-ad45-4246-9817-59bdf7f7ab39",
+ "quantity": 100
+ }
+ ],
+ "itemId": "bc3efcdf-ae18-4037-b0aa-c5af14e03547",
+ "quantity": 10
+ },
+ {
+ // "Game Grid Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "1f7ac0bb-ad45-4246-9817-59bdf7f7ab39",
+ "quantity": 30
+ }
+ ],
+ "itemId": "b51ad015-66e9-41b9-916c-277f9c93232d",
+ "quantity": 10
+ },
+ {
+ // "Luxury Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "1f7ac0bb-ad45-4246-9817-59bdf7f7ab39",
+ "quantity": 100
+ }
+ ],
+ "itemId": "e7f7b758-6492-4c6a-8760-8fb95da530be",
+ "quantity": 1
+ },
+ {
+ // "Smooth Ice",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "30a2288b-e88e-4a92-a916-1edbfc2b2dac",
+ "quantity": 4
+ },
+ {
+ "itemId": "869d4736-289a-4952-96cd-8a40117a2d28",
+ "quantity": 5
+ }
+ ],
+ "itemId": "116325e1-1020-40ec-8f31-0678f8bb98bc",
+ "quantity": 10
+ },
+ {
+ // "Icy Rocks",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "30a2288b-e88e-4a92-a916-1edbfc2b2dac",
+ "quantity": 4
+ },
+ {
+ "itemId": "869d4736-289a-4952-96cd-8a40117a2d28",
+ "quantity": 5
+ }
+ ],
+ "itemId": "b742cff1-98a7-469f-b514-a7402f705924",
+ "quantity": 10
+ },
+ {
+ // "Rubber Block",
+ "craftTime": 15,
+ "ingredientList": [
+ {
+ "itemId": "30a2288b-e88e-4a92-a916-1edbfc2b2dac",
+ "quantity": 4
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 5
+ }
+ ],
+ "itemId": "123425e2-2020-40ec-8f31-0678f8bc99bd",
+ "quantity": 10
+ },
+ {
+ // "Reduced Collision Block",
+ "craftTime": 0,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ }
+ ],
+ "itemId": "bf8f8e24-806b-4223-9345-2bac7c1e265d",
+ "quantity": 1
+ },
+ {
+ // "Vent Block",
+ "craftTime": 0,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ }
+ ],
+ "itemId": "9c40e3ab-8d03-40bc-a88b-eaebc61257d6",
+ "quantity": 1
+ },
+ {
+ // "Invisible Bearing",
+ "craftTime": 30,
+ "ingredientList": [
+ {
+ "itemId": "1f7ac0bb-ad45-4246-9817-59bdf7f7ab39",
+ "quantity": 20
+ }
+ ],
+ "itemId": "ebafd24a-7bbc-4e75-87f5-a67cdcf31e29",
+ "quantity": 1
+ },
+ {
+ // "Steering Pipe",
+ "craftTime": 30,
+ "ingredientList": [
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 20
+ }
+ ],
+ "itemId": "8f762ab9-5c78-4d9c-a22e-3119bf447169",
+ "quantity": 1
+ },
+ {
+ // "Mini Suspension - Short",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "1016cafc-9f6b-40c9-8713-9019d399783f",
+ "quantity": 4
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 5
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 2
+ }
+ ],
+ "itemId": "c0d086eb-de4a-4ba9-922a-1c66f129d2ae",
+ "quantity": 1
+ },
+ {
+ // "Mini Suspension - Long",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "1016cafc-9f6b-40c9-8713-9019d399783f",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 5
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 5
+ }
+ ],
+ "itemId": "8d6e5264-9f90-400a-a471-6c0cae101f4d",
+ "quantity": 1
+ },
+ {
+ // "Suspension - 4x",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "1016cafc-9f6b-40c9-8713-9019d399783f",
+ "quantity": 7
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 5
+ }
+ ],
+ "itemId": "9f016a8d-8cbe-4471-b210-f36f5f280758",
+ "quantity": 1
+ },
+ {
+ // "Suspension - 5x",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "1016cafc-9f6b-40c9-8713-9019d399783f",
+ "quantity": 10
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 5
+ }
+ ],
+ "itemId": "dd676b18-fd06-4aed-baa0-55d4599fdfa3",
+ "quantity": 1
+ },
+ {
+ // "Engine Suspension",
+ "craftTime": 0,
+ "ingredientList": [
+ {
+ "itemId": "1016cafc-9f6b-40c9-8713-9019d399783f",
+ "quantity": 15
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 15
+ }
+ ],
+ "itemId": "b69bf080-2467-4360-9677-72cfd48806c9",
+ "quantity": 1
+ },
+ {
+ // "Mod Piston (256)",
+ "craftTime": 0,
+ "ingredientList": [
+ {
+ "itemId": "1016cafc-9f6b-40c9-8713-9019d399783f",
+ "quantity": 10
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 2
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 15
+ }
+ ],
+ "itemId": "a6d186ad-c9ba-4b6d-922c-6c13dfcea230",
+ "quantity": 1
+ },
+ {
+ // "Motorcycle Seat",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 5
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 4
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "0513fc6d-fb29-46b6-9409-3ec1a4b79c6f",
+ "quantity": 1
+ },
+ {
+ // "Racing Driver Seat",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 5
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 4
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "ffddc616-2799-4883-8c62-193ba30b6fa8",
+ "quantity": 1
+ },
+ {
+ // "Racing Passenger Seat",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 5
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "8c355d48-88cd-467b-863f-12c12b72304d",
+ "quantity": 1
+ },
+ {
+ // "Ship's Wheel Seat",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 10
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "88e045aa-d3ab-435e-b248-60b34c206e6f",
+ "quantity": 1
+ },
+ {
+ // "Jetpack Seat - Or Mech, Gunner, etc",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 5
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 10
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "ad671132-c345-4edc-96c7-ec835ab3a2c9",
+ "quantity": 1
+ },
+ {
+ // "Standing Driver Seat",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 5
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "3b41f562-70c4-4747-9045-3d26ff0e0243",
+ "quantity": 1
+ },
+ {
+ // "Sleeping Bed - Driver",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 20
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 5
+ }
+ ],
+ "itemId": "a3da3841-5a76-4d72-a177-cdc4e572efc7",
+ "quantity": 1
+ },
+ {
+ // "Driver's Pillow",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 5
+ }
+ ],
+ "itemId": "784bf71e-2331-4d56-a70f-b00a3994231b",
+ "quantity": 1
+ },
+ {
+ // "Modular Seat 1 - Standard",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 7
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "b15f557c-872f-4bf5-9c0b-2891a34f430e",
+ "quantity": 1
+ },
+ {
+ // "Modular Seat 2 - Saddle",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 5
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 7
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 7
+ }
+ ],
+ "itemId": "86d5ca1b-f1fa-42a9-9645-0d276e711b27",
+ "quantity": 1
+ },
+ {
+ // "Modular Seat 3 - Race",
+ "craftTime": 57,
+ "ingredientList": [
+ {
+ "itemId": "3440440b-d362-4473-aa03-b7c41e1fe7ad",
+ "quantity": 5
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "d9f49f49-0ef9-41d6-a9a5-ec454be87082",
+ "quantity": 1
+ },
+ {
+ // "Arrow Button",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ }
+ ],
+ "itemId": "685ccdd5-8ee9-480d-a64a-2696c31e1db0",
+ "quantity": 1
+ },
+ {
+ // "Hidden Button Block",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ }
+ ],
+ "itemId": "e426ee20-2cf7-40c9-ad5e-145f43752fab",
+ "quantity": 1
+ },
+ {
+ // "Pull-start Button",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 10
+ }
+ ],
+ "itemId": "f694b406-4f59-46cb-bf33-05e11851eaac",
+ "quantity": 1
+ },
+ {
+ // "Dial Switch",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ }
+ ],
+ "itemId": "deeb37a4-b9ba-4ba7-ad4b-6f5fd5e1d6f2",
+ "quantity": 1
+ },
+ {
+ // "Paddle Latch Switch",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ }
+ ],
+ "itemId": "70e5729d-0706-4c28-80a2-2ab9d7e7532c",
+ "quantity": 1
+ },
+ {
+ // "Door Handle Switch",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ }
+ ],
+ "itemId": "f6619b4a-a627-40c0-939c-a44432ebc66a",
+ "quantity": 1
+ },
+ {
+ // "Throttle Lever - Switch",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 10
+ }
+ ],
+ "itemId": "7022c522-7b33-4638-8dfb-0dfa7c6eb36f",
+ "quantity": 1
+ },
+ {
+ // "Medium Lever - Switch",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 10
+ }
+ ],
+ "itemId": "e9ab70ae-2c6c-4f68-86c3-2f652c180fc5",
+ "quantity": 1
+ },
+ {
+ // "Large Lever - Switch",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 10
+ }
+ ],
+ "itemId": "ee92187e-8363-4ff6-a7ae-c96e60450537",
+ "quantity": 1
+ },
+ {
+ // "Pipe Valve Switch",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ }
+ ],
+ "itemId": "73f35954-e7d2-4a32-bc7c-b88a341ab792",
+ "quantity": 1
+ },
+ {
+ // "Pixel Light Block - Black-Off, Light-On",
+ "craftTime": 2,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 1
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "7947150d-a21c-4195-a4a8-76aab023ba3c",
+ "quantity": 1
+ },
+ {
+ // "Off/On Light - Round",
+ "craftTime": 2,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 1
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "bfbd310d-7c07-4f08-a8cc-9a69e70a9ff0",
+ "quantity": 1
+ },
+ {
+ // "Off/On Light - Rectangle",
+ "craftTime": 2,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 1
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "d7de689e-5082-4323-b6c4-eb1ce8f29719",
+ "quantity": 1
+ },
+ {
+ // "Off/On Light - Round Mini",
+ "craftTime": 2,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 1
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "5a3ea22a-99b1-48eb-b169-3243ad49b27c",
+ "quantity": 1
+ },
+ {
+ // "Off/On Light - Square",
+ "craftTime": 2,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 1
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "7b51eae8-c55c-4102-b9eb-6c23ca547e83",
+ "quantity": 1
+ },
+ {
+ // "Off/On Light - Cube",
+ "craftTime": 2,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 1
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "e83e6fd9-e963-485f-8be0-43895af58ee3",
+ "quantity": 1
+ },
+ {
+ // "Slim Light",
+ "craftTime": 2,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 1
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 1
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "0f994efc-b697-48e4-8ce0-f53eae15d41b",
+ "quantity": 1
+ },
+ {
+ // "Surface Controller",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "9687439e-70a7-4c52-8dd1-d6fa6ec93df8",
+ "quantity": 1
+ },
+ {
+ // "Surface Logic",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "60375a3b-0d70-43a2-a97a-4a4234b500dd",
+ "quantity": 1
+ },
+ {
+ // "Surface Timer",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "f6b70b49-ea25-47cc-9dac-e2dc90e67d07",
+ "quantity": 1
+ },
+ {
+ // "Decoupler",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ }
+ ],
+ "itemId": "f907144d-ae63-4e5e-bff9-f9ca413e2090",
+ "quantity": 1
+ },
+ {
+ // "Time Block ",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "6613bebe-be9f-435c-b415-42a0b45cc8e3",
+ "quantity": 1
+ },
+ {
+ // "Smart Sensor",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "5f41af56-df4c-4837-9b3c-10781335757f",
+ "quantity": 4
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 10
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 6
+ }
+ ],
+ "itemId": "4081ca6f-6b80-4c39-9e79-e1f747039bec",
+ "quantity": 1
+ },
+ {
+ // "Laser Sight",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "c7a99aa6-c5a4-43ad-84c9-c85f7d842a93",
+ "quantity": 1
+ },
+ {
+ // "Tracker",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 10
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 25
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 30
+ }
+ ],
+ "itemId": "6594dff0-c652-4b44-8bb6-c725aaadea05",
+ "quantity": 1
+ },
+ {
+ // "Radar Jammer",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 30
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 20
+ }
+ ],
+ "itemId": "55bb3871-8455-40dc-a968-3dc635a32b7e",
+ "quantity": 1
+ },
+ {
+ // "Wireless Router",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 13
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 20
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 20
+ }
+ ],
+ "itemId": "e627986c-b7dd-4365-8fd8-a0f8707af63d",
+ "quantity": 1
+ },
+ {
+ // "Orientation Block",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "ccaa33b6-e5bb-4edc-9329-b40f6efe2c9e",
+ "quantity": 1
+ },
+ {
+ // "Number Block",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 2
+ }
+ ],
+ "itemId": "efbd14a8-f896-4268-a273-b2b382db520c",
+ "quantity": 1
+ },
+ {
+ // "Math Block\n(Function Block)",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 2
+ }
+ ],
+ "itemId": "289e08ef-e3d8-4f1b-bc10-a0bcf36fa0ce",
+ "quantity": 1
+ },
+ {
+ // "Counter",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 2
+ }
+ ],
+ "itemId": "d8296109-2ffb-4efb-819a-54bd8cadf549",
+ "quantity": 1
+ },
+ {
+ // "Memory Panel",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 2
+ }
+ ],
+ "itemId": "b39faec9-1ed0-4475-8bc0-78d125df1b58",
+ "quantity": 1
+ },
+ {
+ // "x-o-meter",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "d88ba569-f7a5-42e3-bb11-d7979b60ddad",
+ "quantity": 1
+ },
+ {
+ // "Tick Button",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 2
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 2
+ }
+ ],
+ "itemId": "6f2dd83e-bc0d-43f3-8ba5-d5209eb03d07",
+ "quantity": 1
+ },
+ {
+ // "Smart Timer",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "a1d0b1a0-4ec7-4614-8b7a-9a7c521cc03e",
+ "quantity": 1
+ },
+ {
+ // "Keypad Input",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "d3eda549-778f-432b-bf21-65a32ae53378",
+ "quantity": 1
+ },
+ {
+ // "Color Block",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "921a2ace-b543-4ca3-8a9b-6f3dd3132fa9",
+ "quantity": 1
+ },
+ {
+ // "Ascii Block",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "9c47ec58-bdfc-43a4-b066-852156ad812a",
+ "quantity": 1
+ },
+ {
+ // "Ascii Block Inverted",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "8d27520e-2ebc-4f14-bd2c-b99ee8976799",
+ "quantity": 1
+ },
+ {
+ // "Ascii Block Colored",
+ "craftTime": 87,
+ "ingredientList": [
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 10
+ }
+ ],
+ "itemId": "a459eb96-7f0a-42e6-a841-44f9c0561a55",
+ "quantity": 1
+ },
+ {
+ // "Smart Engine Controller",
+ "craftTime": 60,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 40
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 20
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 20
+ }
+ ],
+ "itemId": "fe8978ec-5798-4b24-bdb2-8375387f171b",
+ "quantity": 1
+ },
+ {
+ // "Gas Engine 3-Wide",
+ "craftTime": 60,
+ "ingredientList": [
+ {
+ "itemId": "c0dfdea5-a39d-433a-b94a-299345a5df46",
+ "quantity": 20
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 5
+ }
+ ],
+ "itemId": "70cbacf2-ec5a-471f-9896-0ee944c581d1",
+ "quantity": 1
+ },
+ {
+ // "Pico thruster - Low power",
+ "craftTime": 60,
+ "ingredientList": [
+ {
+ "itemId": "c0dfdea5-a39d-433a-b94a-299345a5df46",
+ "quantity": 20
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 5
+ }
+ ],
+ "itemId": "9235b582-fc25-48a8-a807-68d98755d077",
+ "quantity": 1
+ },
+ {
+ // "1x1 Micro Thruster",
+ "craftTime": 60,
+ "ingredientList": [
+ {
+ "itemId": "c0dfdea5-a39d-433a-b94a-299345a5df46",
+ "quantity": 20
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 5
+ }
+ ],
+ "itemId": "a07a3673-a446-44a3-b16d-abb732c7a525",
+ "quantity": 1
+ },
+ {
+ // "Smart Thruster",
+ "craftTime": 60,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 35
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 15
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 17
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 30
+ }
+ ],
+ "itemId": "a4342028-9a61-4961-99a4-2ae372ee2481",
+ "quantity": 1
+ },
+ {
+ // "WASD Thruster",
+ "craftTime": 60,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 35
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 15
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 25
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 25
+ }
+ ],
+ "itemId": "cec7c353-34ab-43a4-ade5-411df02eaa2a",
+ "quantity": 1
+ },
+ {
+ // "Gimbal Thruster",
+ "craftTime": 60,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 50
+ },
+ {
+ "itemId": "1147e59d-6940-42b4-840b-07f05054f5e0",
+ "quantity": 25
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 15
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 25
+ }
+ ],
+ "itemId": "41d596a6-882c-4f97-b0e0-3e11bff194e2",
+ "quantity": 1
+ },
+ {
+ // "Gravity Module",
+ "craftTime": 37,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 50
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 45
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 30
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 55
+ }
+ ],
+ "itemId": "c9e34f58-b30d-4ab5-92c5-76f707090c45",
+ "quantity": 1
+ },
+ {
+ // "Gravity Modulator",
+ "craftTime": 37,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 50
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 50
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 40
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 60
+ }
+ ],
+ "itemId": "e93ca226-4166-49e3-97cd-bb8df20242d7",
+ "quantity": 1
+ },
+ {
+ // "Large Wing",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 1
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "e2cc7014-85f3-4bb8-a7ca-b70b6502d91e",
+ "quantity": 1
+ },
+ {
+ // "Small Wing / Propeller",
+ "craftTime": 20,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "1bfccc0a-828f-475c-882c-87d5a96054c9",
+ "quantity": 1
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 1
+ }
+ ],
+ "itemId": "b5222776-1702-4c22-a8c5-ca26cc0290e7",
+ "quantity": 1
+ },
+ {
+ // "Timed Explosive - Dynamite",
+ "craftTime": 37,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 10
+ }
+ ],
+ "itemId": "c071509a-9854-4359-8857-296f16edf15d",
+ "quantity": 1
+ },
+ {
+ // "Tone Generator",
+ "craftTime": 37,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 1
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 3
+ }
+ ],
+ "itemId": "f73fd8c7-61eb-416f-8374-9a4c26e6db8f",
+ "quantity": 1
+ },
+ {
+ // "Smart Totebot",
+ "craftTime": 37,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 5
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 1
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 4
+ }
+ ],
+ "itemId": "1d5d997e-efa9-478c-98ee-e452f5ff0dd2",
+ "quantity": 1
+ },
+ {
+ // "Radar",
+ "craftTime": 37,
+ "ingredientList": [
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 10
+ },
+ {
+ "itemId": "f152e4df-bc40-44fb-8d20-3b3ff70cdfe3",
+ "quantity": 5
+ },
+ {
+ "itemId": "5530e6a0-4748-4926-b134-50ca9ecb9dcf",
+ "quantity": 3
+ },
+ {
+ "itemId": "36335664-6e61-4d44-9876-54f9660a8565",
+ "quantity": 10
+ }
+ ],
+ "itemId": "a053e38d-608b-4b9a-8d04-c3da2b6fdff7",
+ "quantity": 1
+ },
+ {
+ // "Roller Wheel - 1x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 3
+ }
+ ],
+ "itemId": "b57c8185-2f95-4ba8-818d-eed258e01d00",
+ "quantity": 1
+ },
+ {
+ // "Roller Wheel - 2x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 6
+ }
+ ],
+ "itemId": "4e1452d0-a9c2-42d4-91cf-1aea84c533ed",
+ "quantity": 1
+ },
+ {
+ // "Rounded Wheel - 3x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 15
+ }
+ ],
+ "itemId": "c460f40f-c7b7-4695-a627-9f01749724b6",
+ "quantity": 1
+ },
+ {
+ // "Hubless Wheel",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 15
+ }
+ ],
+ "itemId": "c9cf3b88-86f3-4146-af16-a7b16c917ecc",
+ "quantity": 1
+ },
+ {
+ // "Invisible Wheel",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 30
+ }
+ ],
+ "itemId": "4358b01f-3303-42a2-91d2-cc950e23b59b",
+ "quantity": 1
+ },
+ {
+ // "Dirt Wheel - 3x1/2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 13
+ }
+ ],
+ "itemId": "27b6541d-2be0-420f-abb8-d59f566e85bd",
+ "quantity": 1
+ },
+ {
+ // "Dirt Wheel - 3x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 15
+ }
+ ],
+ "itemId": "68a7fe0a-4d48-4552-a29d-044d3c7b9e02",
+ "quantity": 1
+ },
+ {
+ // "Dirt Wheel - 4x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 16
+ }
+ ],
+ "itemId": "92d3c375-0e45-4df4-8691-90e49e8ce24d",
+ "quantity": 1
+ },
+ {
+ // "Dirt Wheel - 5x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 18
+ }
+ ],
+ "itemId": "041ae0ae-07d5-4ac5-9767-be396ba2445d",
+ "quantity": 1
+ },
+ {
+ // "Dirt Wheel - 5x2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 20
+ }
+ ],
+ "itemId": "c47ff0cd-e1f2-4727-ba40-c85d0512d6d0",
+ "quantity": 1
+ },
+ {
+ // "Dirt Wheel - 5x3",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 20
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 21
+ }
+ ],
+ "itemId": "784bf162-b2d1-4a6d-9cf4-091827871a40",
+ "quantity": 1
+ },
+ {
+ // "Street Wheel - 3x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 15
+ }
+ ],
+ "itemId": "5a9ff028-a6cb-4010-bcd4-c88eb230bcdb",
+ "quantity": 1
+ },
+ {
+ // "Street Wheel - 4x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 17
+ }
+ ],
+ "itemId": "58e7fdd0-9f15-466c-aaba-60805044fe67",
+ "quantity": 1
+ },
+ {
+ // "Street Wheel - 4x2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 18
+ }
+ ],
+ "itemId": "3147a872-cb90-487c-b6ce-438c6403b09c",
+ "quantity": 1
+ },
+ {
+ // "Street Wheel - 5x2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 20
+ }
+ ],
+ "itemId": "a02f96b2-26c3-4b3e-a00c-32254af91a5d",
+ "quantity": 1
+ },
+ {
+ // "Street Wheel - 6x2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 22
+ }
+ ],
+ "itemId": "d3954d83-78ee-4fbf-9a60-70e4872e1d39",
+ "quantity": 1
+ },
+ {
+ // "Combat Wheel - 3x1",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 25
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 15
+ }
+ ],
+ "itemId": "d59bd448-6401-4476-849a-e6fc86d28b7b",
+ "quantity": 1
+ },
+ {
+ // "Combat Wheel - 5x2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 30
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 20
+ }
+ ],
+ "itemId": "96fc86b1-57e7-4795-9d39-90e74f52ba87",
+ "quantity": 1
+ },
+ {
+ // "Combat Wheel - 7x2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 30
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 28
+ }
+ ],
+ "itemId": "fafdc38f-e374-49fd-8fea-4062a4b5c93a",
+ "quantity": 1
+ },
+ {
+ // "Combat Wheel - 9x3",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 30
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 30
+ }
+ ],
+ "itemId": "4dee7b84-ee4c-42ce-9a42-4c6ced837ff5",
+ "quantity": 1
+ },
+ {
+ // "Combat Wheel - 11x4",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 30
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 32
+ }
+ ],
+ "itemId": "583f20c9-a7da-477b-b852-aac404467e3a",
+ "quantity": 1
+ },
+ {
+ // "Combat Wheel - 13x5",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 30
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 35
+ }
+ ],
+ "itemId": "90bc9601-b874-45e2-8970-09d5e5ff3ca9",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 3x2",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 20
+ }
+ ],
+ "itemId": "abe5a33f-a3cc-41a8-b32a-731ec2b184c1",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 5x3",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 25
+ }
+ ],
+ "itemId": "0761c82c-7a3f-4c22-8ec1-46223f10a2de",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 7x4",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 30
+ }
+ ],
+ "itemId": "ee8869cb-9cce-43c3-a04a-e8b37b9de0ed",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 9x5",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 35
+ }
+ ],
+ "itemId": "2210586c-3a93-43ff-b861-542e2d2d12d6",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 11x6",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 40
+ }
+ ],
+ "itemId": "6afcb463-925a-4a2e-b9bf-3397ef094df9",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 13x6",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 45
+ }
+ ],
+ "itemId": "b0f71aed-04f0-4bba-b969-cba57f3fc667",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 15x7",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 50
+ }
+ ],
+ "itemId": "87f8f460-eba5-43c1-a1ad-facfb3d3f376",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 17x8",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 55
+ }
+ ],
+ "itemId": "2bcc8c8f-1e94-4f9d-97b9-6e7423ca40b0",
+ "quantity": 1
+ },
+ {
+ // "Sand Wheel - 19x9",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 15
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 60
+ }
+ ],
+ "itemId": "61a70b37-ea24-4022-b1e6-45a5a3ae179d",
+ "quantity": 1
+ },
+ {
+ // "Big Wheel - 7x3",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 40
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 30
+ }
+ ],
+ "itemId": "036fc6d2-45df-11e6-beb8-9e71128cae77",
+ "quantity": 1
+ },
+ {
+ // "Big Wheel - 9x4",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 40
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 35
+ }
+ ],
+ "itemId": "036fc79a-45df-11e6-beb8-9e71128cae77",
+ "quantity": 1
+ },
+ {
+ // "Chained Wheel",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 50
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 50
+ }
+ ],
+ "itemId": "d739fdf9-2c61-47ea-88ab-beac6a6c35c0",
+ "quantity": 1
+ },
+ {
+ // "Spiked Wheel",
+ "craftTime": 27,
+ "ingredientList": [
+ {
+ "itemId": "df953d9c-234f-4ac2-af5e-f0490b223e71",
+ "quantity": 15
+ },
+ {
+ "itemId": "8aedf6c2-94e1-4506-89d4-a0227c552f1e",
+ "quantity": 60
+ },
+ {
+ "itemId": "fcf0958c-084d-4854-9b1b-b06594b4262a",
+ "quantity": 60
+ }
+ ],
+ "itemId": "d739fdf9-2c61-47ea-88ab-beac6a6c35c1",
+ "quantity": 1
+ }
+]
\ No newline at end of file
diff --git a/Gui/IconMap.xml b/Gui/IconMap.xml
index e49f441..4f10f83 100644
--- a/Gui/IconMap.xml
+++ b/Gui/IconMap.xml
@@ -59,6 +59,9 @@
%s
" function Gimball.client_canInteract(self) - sm.gui.setInteractionText("Press", sm.gui.getKeyBinding("Use"), "to change mode") - sm.gui.setInteractionText("Current mode: "..self.modes[self.mode+1]) + local use_key = sm.gui.getKeyBinding("Use") + local crawl_key = sm.gui.getKeyBinding("Crawl") + + local use_hyper = default_hypertext:format(use_key) + local crawl_and_use_hyper = default_hypertext:format(crawl_key.." + "..use_key) + + sm.gui.setInteractionText("Press", use_hyper, "or", crawl_and_use_hyper, "to change mode") + + local cur_mode_hyper = default_hypertext:format("Mode: "..self.modes[self.mode+1]) + sm.gui.setInteractionText("", cur_mode_hyper) + return true end +local gb_logic_and_power = bit.bor(sm.interactable.connectionType.logic, sm.interactable.connectionType.power) function Gimball.client_onFixedUpdate(self, dt) - local parents = self.interactable:getParents() + local parents = self.interactable:getParents(gb_logic_and_power) local power = #parents>0 and 100 or 0 local hasnumber = false local logicinput = 1 @@ -178,9 +242,15 @@ function Gimball.client_onFixedUpdate(self, dt) end end - self.power = power * logicinput * canfire - if math.abs(self.power) == math.huge or self.power ~= self.power then self.power = 0 end - + if self.cl_can_activate then + self.power = power * logicinput * canfire + else + self.power = 0 + end + + if math.abs(self.power) == math.huge or self.power ~= self.power then + self.power = 0 + end if self.mode == 0 then if ws then self.angleX = ws*90 end @@ -268,10 +338,10 @@ function Gimball.client_onFixedUpdate(self, dt) ]] - if false then --visualise x and z + --[[if false then --visualise x and z sm.particle.createParticle("construct_welding", self.shape.worldPosition + localX) sm.particle.createParticle("construct_welding", self.shape.worldPosition + localZ) - end + end]] -- direction to pose translation: local localvec = getLocal(self.shape, self.direction*-1) diff --git a/Scripts/interactable/Locomotion/ModGasEngine.lua b/Scripts/interactable/Locomotion/ModGasEngine.lua new file mode 100644 index 0000000..9bafeb5 --- /dev/null +++ b/Scripts/interactable/Locomotion/ModGasEngine.lua @@ -0,0 +1,648 @@ +-- GasEngine.lua -- +dofile("$SURVIVAL_DATA/Scripts/game/survival_constants.lua") +dofile("$SURVIVAL_DATA/Scripts/game/survival_items.lua") + +dofile("$SURVIVAL_DATA/Scripts/util.lua") + +GasEngine = class() +GasEngine.maxParentCount = 2 +GasEngine.maxChildCount = 255 +GasEngine.connectionInput = sm.interactable.connectionType.logic + sm.interactable.connectionType.power + sm.interactable.connectionType.gasoline +GasEngine.connectionOutput = sm.interactable.connectionType.bearing +GasEngine.colorNormal = sm.color.new( 0xff8000ff ) +GasEngine.colorHighlight = sm.color.new( 0xff9f3aff ) +GasEngine.poseWeightCount = 1 + +local Gears = { + { power = 0 }, + { power = 30 }, + { power = 60 }, + { power = 90 }, + { power = 150 }, -- 1 + { power = 240 }, + { power = 390 }, -- 2 + { power = 630 }, + { power = 1020 }, -- 3 + { power = 1650 }, + { power = 2670 }, -- 4 + { power = 4320 }, + { power = 6990 }, -- 5 +} + +local GasEngine3WideGears = { + { power = 0 }, + { power = 8 }, + { power = 16 }, + { power = 32 }, + { power = 64 }, + { power = 128 }, + { power = 256 }, + { power = 512 }, + { power = 1024 } +} + +local EngineSuspensionGears = { + { power = 0 }, + { power = 8 }, + { power = 16 }, + { power = 32 }, + { power = 64 }, + { power = 128 }, + { power = 256 }, + { power = 512 }, + { power = 1024 }, + { power = 2048 }, + { power = 4096 }, + { power = 8192 }, + { power = 16384 }, + { power = 32768 }, + { power = 65536 }, + { power = 131072 }, + { power = 262144 } +} + +local EngineLevels = { + ["70cbacf2-ec5a-471f-9896-0ee944c581d1"] = { --Gas Engine 3-Wide + gears = GasEngine3WideGears, + gearCount = #GasEngine3WideGears, + title = "Modpack Continuation", + bearingCount = 5, + pointsPerFuel = 15000, + effect = "GasEngine - Level 1" + }, + ["b69bf080-2467-4360-9677-72cfd48806c9"] = { --Engine Suspension + gears = EngineSuspensionGears, + gearCount = #EngineSuspensionGears, + title = "Modpack Continuation", + bearingCount = 5, + pointsPerFuel = 11000, + effect = "GasEngine - Level 1" + } +} + +local RadPerSecond_100KmPerHourOn3BlockDiameterTyres = 74.074074 +local RadPerSecond_1MeterPerSecondOn3BlockDiameterTyres = 2.6666667 + +--[[ Server ]] + +function GasEngine.server_onCreate( self ) + local container = self.shape.interactable:getContainer( 0 ) + if not container then + container = self.shape:getInteractable():addContainer( 0, 1, 10 ) + end + container:setFilters( { obj_consumable_gas } ) + + local level = EngineLevels[tostring( self.shape:getShapeUuid() )] + assert(level) + if level.fn then + level.fn( self ) + end + + self.scrapOffset = 0 + self.pointsPerFuel = level.pointsPerFuel + self.gears = level.gears + self:server_init() +end + +function GasEngine.server_onRefresh( self ) + self:server_init() +end + +function GasEngine.server_init( self ) + + self.saved = self.storage:load() + if self.saved == nil then + self.saved = {} + end + if self.saved.gearIdx == nil then + self.saved.gearIdx = 1 + end + if self.saved.fuelPoints == nil then + self.saved.fuelPoints = 0 + end + + self.power = 0 + self.motorVelocity = 0 + self.motorImpulse = 0 + self.fuelPoints = self.saved.fuelPoints + self.hasFuel = false + self.dirtyStorageTable = false + self.dirtyClientTable = false + + self:sv_setGear( self.saved.gearIdx ) +end + +function GasEngine.sv_setGear( self, gearIdx ) + self.saved.gearIdx = gearIdx + self.dirtyStorageTable = true + self.dirtyClientTable = true +end + +function GasEngine.sv_updateFuelStatus( self, fuelContainer ) + + if self.saved.fuelPoints ~= self.fuelPoints then + self.saved.fuelPoints = self.fuelPoints + self.sv_fuel_save_timer = 1 + end + + local hasFuel = ( self.fuelPoints > 0 ) or sm.container.canSpend( fuelContainer, obj_consumable_gas, 1 ) + if self.hasFuel ~= hasFuel then + self.hasFuel = hasFuel + self.dirtyClientTable = true + end + +end + +function GasEngine.controlEngine( self, direction, active, timeStep, gearIdx ) + direction = clamp( direction, -1, 1 ) + if ( math.abs( direction ) > 0 or not active ) then + self.power = self.power + timeStep + else + self.power = self.power - timeStep + end + self.power = clamp( self.power, 0, 1 ) + + if direction == 0 and active then + self.power = 0 + end + + self.motorVelocity = ( active and direction or 0 ) * RadPerSecond_100KmPerHourOn3BlockDiameterTyres + self.motorImpulse = ( active and self.power or 2 ) * self.gears[gearIdx].power +end + +function GasEngine.getInputs( self ) + + local parents = self.interactable:getParents() + local active = true + local direction = 1 + local fuelContainer = nil + local hasInput = false + if parents[2] then + if parents[2]:hasOutputType( sm.interactable.connectionType.logic ) then + active = parents[2]:isActive() + hasInput = true + end + if parents[2]:hasOutputType( sm.interactable.connectionType.power ) then + active = parents[2]:isActive() + direction = parents[2]:getPower() + hasInput = true + end + if parents[2]:hasOutputType( sm.interactable.connectionType.gasoline ) then + fuelContainer = parents[2]:getContainer( 0 ) + end + end + if parents[1] then + if parents[1]:hasOutputType( sm.interactable.connectionType.logic ) then + active = parents[1]:isActive() + hasInput = true + end + if parents[1]:hasOutputType( sm.interactable.connectionType.power ) then + active = parents[1]:isActive() + direction = parents[1]:getPower() + hasInput = true + end + if parents[1]:hasOutputType( sm.interactable.connectionType.gasoline ) then + fuelContainer = parents[1]:getContainer( 0 ) + end + end + + return active, direction, fuelContainer, hasInput + +end + +function GasEngine.server_onFixedUpdate( self, timeStep ) + + -- Check engine connections + local hadInput = self.hasInput == nil and true or self.hasInput --Pretend to have had input if nil to avoid starting engines at load + local active, direction, fuelContainer, hasInput = self:getInputs() + self.hasInput = hasInput + local useCreativeFuel = not sm.game.getEnableFuelConsumption() and fuelContainer == nil + + -- Check fuel container + if not fuelContainer or fuelContainer:isEmpty() then + fuelContainer = self.shape.interactable:getContainer( 0 ) + end + + -- Check bearings + local bearings = {} + local joints = self.interactable:getJoints() + for _, joint in ipairs( joints ) do + if joint:getType() == "bearing" then + bearings[#bearings+1] = joint + end + end + + -- Update motor gear when a steering is added + if not hadInput and hasInput then + if self.saved.gearIdx == 1 then + self:sv_setGear( 2 ) + end + end + + -- Consume fuel for fuel points + local canSpend = false + if self.fuelPoints <= 0 then + canSpend = sm.container.canSpend( fuelContainer, obj_consumable_gas, 1 ) + end + + -- Control engine + if self.fuelPoints > 0 or canSpend or useCreativeFuel then + + if hasInput == false then + self.power = 1 + self:controlEngine( 1, true, timeStep, self.saved.gearIdx ) + else + self:controlEngine( direction, active, timeStep, self.saved.gearIdx ) + end + + if not useCreativeFuel then + -- Consume fuel points + local appliedImpulseCost = 0.015625 + local fuelCost = 0 + for _, bearing in ipairs( bearings ) do + if bearing.appliedImpulse * bearing.angularVelocity < 0 then -- No added fuel cost if the bearing is decelerating + fuelCost = fuelCost + math.abs( bearing.appliedImpulse ) * appliedImpulseCost + end + end + fuelCost = math.min( fuelCost, math.sqrt( fuelCost / 7.5 ) * 7.5 ) + + self.fuelPoints = self.fuelPoints - fuelCost + + if self.fuelPoints <= 0 and fuelCost > 0 then + sm.container.beginTransaction() + sm.container.spend( fuelContainer, obj_consumable_gas, 1, true ) + if sm.container.endTransaction() then + self.fuelPoints = self.fuelPoints + self.pointsPerFuel + end + end + end + + else + self:controlEngine( 0, false, timeStep, self.saved.gearIdx ) + end + + -- Update rotational joints + for _, bearing in ipairs( bearings ) do + bearing:setMotorVelocity( self.motorVelocity, self.motorImpulse ) + end + + self:sv_updateFuelStatus( fuelContainer ) + + if self.sv_fuel_save_timer ~= nil then + self.sv_fuel_save_timer = self.sv_fuel_save_timer - timeStep + + if self.sv_fuel_save_timer < 0 then + self.sv_fuel_save_timer = nil + self.dirtyStorageTable = true + end + end + + -- Storage table dirty + if self.dirtyStorageTable then + self.storage:save( self.saved ) + self.dirtyStorageTable = false + end + + -- Client table dirty + if self.dirtyClientTable then + self.network:setClientData( { gearIdx = self.saved.gearIdx, engineHasFuel = self.hasFuel or useCreativeFuel, scrapOffset = self.scrapOffset } ) + self.dirtyClientTable = false + end +end + +--[[ Client ]] + +function GasEngine.client_onCreate( self ) + local level = EngineLevels[tostring( self.shape:getShapeUuid() )] + self.gears = level.gears + self.client_gearIdx = 1 + self.effect = sm.effect.createEffect( level.effect, self.interactable ) + self.engineHasFuel = false + self.scrapOffset = self.scrapOffset or 0 + self.power = 0 +end + +function GasEngine.client_onClientDataUpdate( self, params ) + + if self.gui then + if self.gui:isActive() and params.gearIdx ~= self.client_gearIdx then + self.gui:setSliderPosition("Setting", params.gearIdx - 1 ) + end + end + + self.client_gearIdx = params.gearIdx + self.interactable:setPoseWeight( 0, params.gearIdx / #self.gears ) + + if self.engineHasFuel and not params.engineHasFuel then + local character = sm.localPlayer.getPlayer().character + if character then + if ( self.shape.worldPosition - character.worldPosition ):length2() < 100 then + sm.gui.displayAlertText( "#{INFO_OUT_OF_FUEL}" ) + end + end + end + + if params.engineHasFuel then + self.effect:setParameter("gas", 0.0 ) + else + self.effect:setParameter("gas", 1.0 ) + end + + self.engineHasFuel = params.engineHasFuel + self.scrapOffset = params.scrapOffset +end + +function GasEngine.client_onDestroy( self ) + self.effect:destroy() + + if self.gui then + self.gui:close() + self.gui:destroy() + self.gui = nil + end +end + +function GasEngine.client_onFixedUpdate( self, timeStep ) + + local active, direction, externalFuelTank, hasInput = self:getInputs() + + + if self.gui then + self.gui:setVisible( "FuelContainer", externalFuelTank ~= nil ) + end + + if sm.isHost then + return + end + + -- Check bearings + local bearings = {} + local joints = self.interactable:getJoints() + for _, joint in ipairs( joints ) do + if joint:getType() == "bearing" then + bearings[#bearings+1] = joint + end + end + + -- Control engine + if self.engineHasFuel then + if hasInput == false then + self.power = 1 + + self:controlEngine( 1, true, timeStep, self.client_gearIdx ) + else + + self:controlEngine( direction, active, timeStep, self.client_gearIdx ) + end + else + self:controlEngine( 0, false, timeStep, self.client_gearIdx ) + end + + -- Update rotational joints + for _, bearing in ipairs( bearings ) do + bearing:setMotorVelocity( self.motorVelocity, self.motorImpulse ) + end +end + +function GasEngine.client_onUpdate( self, dt ) + + local active, direction = self:getInputs() + + self:cl_updateEffect( direction, active ) +end + +function GasEngine.client_onInteract( self, character, state ) + if state == true then + self.gui = sm.gui.createEngineGui() + + self.gui:setText( "Name", "#{CONTROLLER_ENGINE_GAS_TITLE}" ) + self.gui:setText( "Interaction", "#{CONTROLLER_ENGINE_INSTRUCTION}" ) + self.gui:setOnCloseCallback( "cl_onGuiClosed" ) + self.gui:setSliderCallback( "Setting", "cl_onSliderChange" ) + self.gui:setSliderData( "Setting", #self.gears, self.client_gearIdx - 1 ) + self.gui:setIconImage( "Icon", self.shape:getShapeUuid() ) + self.gui:setButtonCallback( "Upgrade", "cl_onUpgradeClicked" ) + + local fuelContainer = self.shape.interactable:getContainer( 0 ) + + if fuelContainer then + self.gui:setContainer( "Fuel", fuelContainer ) + end + + local _, _, externalFuelContainer, _ = self:getInputs() + if externalFuelContainer then + self.gui:setVisible( "FuelContainer", true ) + end + + if not sm.game.getEnableFuelConsumption() then + self.gui:setVisible( "BackgroundGas", false ) + self.gui:setVisible( "FuelGrid", false ) + end + + self.gui:open() + + local level = EngineLevels[ tostring( self.shape:getShapeUuid() ) ] + if level then + if level.upgrade then + local nextLevel = EngineLevels[ level.upgrade ] + self.gui:setData( "UpgradeInfo", { Gears = nextLevel.gearCount - level.gearCount, Bearings = nextLevel.bearingCount - level.bearingCount, Efficiency = 1 } ) + self.gui:setIconImage( "UpgradeIcon", sm.uuid.new( level.upgrade ) ) + else + self.gui:setVisible( "UpgradeIcon", false ) + self.gui:setData( "UpgradeInfo", nil ) + end + + self.gui:setVisible("SubTitle", false) + self.gui:setSliderRangeLimit( "Setting", level.gearCount ) + + if sm.game.getEnableUpgrade() and level.cost then + local inventory = sm.localPlayer.getPlayer():getInventory() + local availableKits = sm.container.totalQuantity( inventory, obj_consumable_component ) + local upgradeData = { cost = level.cost, available = availableKits } + self.gui:setData( "Upgrade", upgradeData ) + else + self.gui:setVisible( "Upgrade", false ) + end + end + end +end + +function GasEngine.client_getAvailableParentConnectionCount( self, connectionType ) + if bit.band( connectionType, bit.bor( sm.interactable.connectionType.logic, sm.interactable.connectionType.power ) ) ~= 0 then + return 1 - #self.interactable:getParents( bit.bor( sm.interactable.connectionType.logic, sm.interactable.connectionType.power ) ) + end + if bit.band( connectionType, sm.interactable.connectionType.gasoline ) ~= 0 then + return 1 - #self.interactable:getParents( sm.interactable.connectionType.gasoline ) + end + return 0 +end + +function GasEngine.client_getAvailableChildConnectionCount( self, connectionType ) + if connectionType ~= sm.interactable.connectionType.bearing then + return 0 + end + local level = EngineLevels[tostring( self.shape:getShapeUuid() )] + assert(level) + local maxBearingCount = level.bearingCount or 255 + return maxBearingCount - #self.interactable:getChildren( sm.interactable.connectionType.bearing ) +end + +function GasEngine.cl_onGuiClosed( self ) + self.gui:destroy() + self.gui = nil +end + +function GasEngine.cl_onSliderChange( self, sliderName, sliderPos ) + self.network:sendToServer( "sv_setGear", sliderPos + 1 ) + self.client_gearIdx = sliderPos + 1 +end + +function GasEngine.cl_onUpgradeClicked( self, buttonName ) + print( "upgrade clicked" ) + self.network:sendToServer("sv_n_tryUpgrade", sm.localPlayer.getPlayer() ) +end + +function GasEngine.cl_updateEffect( self, direction, active ) + local bearings = {} + local joints = self.interactable:getJoints() + for _, joint in ipairs( joints ) do + if joint:getType() == "bearing" then + bearings[#bearings+1] = joint + end + end + + local RadPerSecond_100KmPerHourOn3BlockDiameterTyres = 74.074074 + local avgImpulse = 0 + local avgVelocity = 0 + + if #bearings > 0 then + for _, currentBearing in ipairs( bearings ) do + avgImpulse = avgImpulse + math.abs( currentBearing.appliedImpulse ) + avgVelocity = avgVelocity + math.abs( currentBearing.angularVelocity ) + end + + avgImpulse = avgImpulse / #bearings + avgVelocity = avgVelocity / #bearings + + avgVelocity = math.min( avgVelocity, RadPerSecond_100KmPerHourOn3BlockDiameterTyres ) + end + + local impulseFraction = 0 + local velocityFraction = avgVelocity / ( RadPerSecond_100KmPerHourOn3BlockDiameterTyres / 1.2 ) + + if direction ~= 0 and self.gears[self.client_gearIdx].power > 0 then + impulseFraction = math.abs( avgImpulse ) / self.gears[self.client_gearIdx].power + end + + local maxRPM = 0.9 * (self.client_gearIdx / #self.gears) + local rpm = 0.1 + + if avgVelocity > 0 then + rpm = rpm + math.min( velocityFraction * maxRPM, maxRPM ) + end + + local engineLoad = 0 + + if direction ~= 0 then + engineLoad = impulseFraction - math.min( velocityFraction, 1.0 ) + end + + local onLift = self.shape:getBody():isOnLift() + if #self.interactable:getParents() == 0 then + if self.effect:isPlaying() == false and #bearings > 0 and not onLift and self.gears[self.client_gearIdx].power > 0 then + self.effect:start() + elseif self.effect:isPlaying() and ( #bearings == 0 or onLift or self.gears[self.client_gearIdx].power == 0 ) then + self.effect:setParameter( "load", 0.5 ) + self.effect:setParameter( "rpm", 0 ) + self.effect:stop() + end + else + if self.effect:isPlaying() and ( #bearings == 0 or onLift or active == false or self.gears[self.client_gearIdx].power == 0 ) then + self.effect:setParameter( "load", 0.5 ) + self.effect:setParameter( "rpm", 0 ) + self.effect:stop() + elseif self.effect:isPlaying() == false and #bearings > 0 and not onLift and active == true and self.gears[self.client_gearIdx].power > 0 then + self.effect:start() + end + end + + if self.effect:isPlaying() then + self.effect:setParameter( "rpm", rpm ) + self.effect:setParameter( "load", engineLoad * 0.5 + 0.5 ) + end +end + +function GasEngine.sv_n_tryUpgrade( self, player ) + + local level = EngineLevels[tostring( self.shape:getShapeUuid() )] + if level and level.upgrade then + local function fnUpgrade() + local nextLevel = EngineLevels[level.upgrade] + assert( nextLevel ) + self.gears = nextLevel.gears + self.network:sendToClients( "cl_n_onUpgrade", level.upgrade ) + + if nextLevel.fn then + nextLevel.fn( self ) + end + + self.shape:replaceShape( sm.uuid.new( level.upgrade ) ) + end + + if sm.game.getEnableUpgrade() then + local inventory = player:getInventory() + + if sm.container.totalQuantity( inventory, obj_consumable_component ) >= level.cost then + + if sm.container.beginTransaction() then + sm.container.spend( inventory, obj_consumable_component, level.cost, true ) + + if sm.container.endTransaction() then + fnUpgrade() + end + end + else + print( "Cannot afford upgrade" ) + end + end + else + print( "Can't be upgraded" ) + end + +end + +function GasEngine.cl_n_onUpgrade( self, upgrade ) + local level = EngineLevels[upgrade] + self.gears = level.gears + self.pointsPerFuel = level.pointsPerFuel + + if self.gui and self.gui:isActive() then + self.gui:setIconImage( "Icon", sm.uuid.new( upgrade ) ) + + if sm.game.getEnableUpgrade() and level.cost then + local inventory = sm.localPlayer.getPlayer():getInventory() + local availableKits = sm.container.totalQuantity( inventory, obj_consumable_component ) + local upgradeData = { cost = level.cost, available = availableKits } + self.gui:setData( "Upgrade", upgradeData ) + else + self.gui:setVisible( "Upgrade", false ) + end + + self.gui:setText( "SubTitle", level.title ) + self.gui:setSliderRangeLimit( "Setting", level.gearCount ) + if level.upgrade then + local nextLevel = EngineLevels[ level.upgrade ] + self.gui:setData( "UpgradeInfo", { Gears = nextLevel.gearCount - level.gearCount, Bearings = nextLevel.bearingCount - level.bearingCount, Efficiency = 1 } ) + self.gui:setIconImage( "UpgradeIcon", sm.uuid.new( level.upgrade ) ) + else + self.gui:setVisible( "UpgradeIcon", false ) + self.gui:setData( "UpgradeInfo", nil ) + end + end + + if self.effect then + --self.effect:destroy() + end + self.effect = sm.effect.createEffect( level.effect, self.interactable ) + sm.effect.playHostedEffect( "Part - Upgrade", self.interactable ) +end \ No newline at end of file diff --git a/Scripts/interactable/Locomotion/ModThruster.lua b/Scripts/interactable/Locomotion/ModThruster.lua new file mode 100644 index 0000000..fd1099c --- /dev/null +++ b/Scripts/interactable/Locomotion/ModThruster.lua @@ -0,0 +1,319 @@ +dofile("$SURVIVAL_DATA/Scripts/game/survival_items.lua") + +ModThruster = class() +ModThruster.maxParentCount = 3 +ModThruster.maxChildCount = 0 +ModThruster.connectionInput = sm.interactable.connectionType.logic + sm.interactable.connectionType.power + sm.interactable.connectionType.gasoline +ModThruster.connectionOutput = sm.interactable.connectionType.none +ModThruster.colorNormal = sm.color.new(0x29CCCDff) +ModThruster.colorHighlight = sm.color.new(0x36F3F7ff) +ModThruster.poseWeightCount = 3 + + +local PicoThrusterGears = { + { averageForce = 800 }, + { averageForce = 1250 }, + { averageForce = 1500 }, + { averageForce = 2000 }, + { averageForce = 3000 }, + { averageForce = 4000 }, + { averageForce = 5000 }, + { averageForce = 7000 }, + { averageForce = 9000 }, + { averageForce = 11000 }, + { averageForce = 15000 }, + { averageForce = 20000 }, + { averageForce = 25000 } +} + +local OneByOneMicroThruster = { + { averageForce = 2222.22 }, + { averageForce = 3333.33 }, + { averageForce = 5000 }, + { averageForce = 7500 }, + { averageForce = 11250 }, + { averageForce = 16875 }, + { averageForce = 25312.5 }, + { averageForce = 37968.5 }, + { averageForce = 56953.25 }, + { averageForce = 85429.875 }, + { averageForce = 128144.31 }, + { averageForce = 192216.97 }, + { averageForce = 288325.96 } +} + +local thruster_uuid_data = { + ["9235b582-fc25-48a8-a807-68d98755d077"] = { + gears = PicoThrusterGears, + gear_count = #PicoThrusterGears, + effect_offset = sm.vec3.new(0, 0, -0.57) + }, + ["a07a3673-a446-44a3-b16d-abb732c7a525"] = { + gears = OneByOneMicroThruster, + gear_count = #OneByOneMicroThruster, + effect_offset = sm.vec3.new(0, 0, -0.3) + } +} + +function ModThruster:client_onCreate() + local cur_data = thruster_uuid_data[tostring(self.shape.uuid)] + self.gears = cur_data.gears + self.gear_count = cur_data.gear_count + + self.thrust_effect = sm.effect.createEffect("Thruster - Level 1", self.interactable) + self.thrust_effect:setOffsetPosition(cur_data.effect_offset) + + self.cl_thruster_active = false + self.cl_cur_gear = 0 + self.cl_time_val = 0 + self.cl_time_val2 = 0 +end + +function ModThruster:client_onDestroy() + if self.thrust_effect:isPlaying() then + self.thrust_effect:stopImmediate() + end + + if self.gui then + self.gui:close() + self.gui:destroy() + self.gui = nil + end + + self.thrust_effect:destroy() +end + +function ModThruster:client_onClientDataUpdate(params) + local is_active = params[2] + self.cl_thruster_active = is_active + self.cl_cur_gear = params[1] + + local cur_weight = self.cl_cur_gear / (self.gear_count - 1) + self.interactable:setPoseWeight(2, cur_weight) + + local th_effect = self.thrust_effect + + if is_active then + if not th_effect:isPlaying() then + th_effect:start() + end + else + if th_effect:isPlaying() then + th_effect:stop() + end + end +end + +function ModThruster:client_onFixedUpdate() + if self.cl_thruster_active then + local vel_length = sm.util.clamp(self.shape.velocity:length2() / 25, 0, 50) + self.thrust_effect:setParameter("velocity", vel_length) + + self.cl_time_val = self.cl_time_val + 0.2 + self.cl_time_val2 = self.cl_time_val2 + 0.8 + + local light_noise = sm.noise.simplexNoise1d(self.cl_time_val) * 0.5 + self.thrust_effect:setParameter("intensity", 2 + light_noise) + + local thruster_noise = math.abs(math.sin(self.cl_time_val2)) * 0.4 + self.interactable:setPoseWeight(1, thruster_noise) + else + self.interactable:setPoseWeight(1, 0) + end +end + +function ModThruster:client_onSliderChange(widget, value) + self.cl_cur_gear = value + self.network:sendToServer("server_setGear", value) +end + +function ModThruster:client_onGuiClose() + self.gui:destroy() + self.gui = nil +end + +function ModThruster:client_onInteract(character, state) + if state then + local s_gui = sm.gui.createEngineGui() + + s_gui:setText("Name", "#{CONTROLLER_THRUSTER_TITLE}") + s_gui:setText("Interaction", "#{CONTROLLER_THRUSTER_INSTRUCTION}") + s_gui:setSliderCallback("Setting", "client_onSliderChange") + s_gui:setOnCloseCallback("client_onGuiClose") + s_gui:setSliderData("Setting", self.gear_count, self.cl_cur_gear) + s_gui:setIconImage("Icon", self.shape:getShapeUuid()) + s_gui:setVisible("SubTitle", false) + + local fuelContainer = self.interactable:getContainer(0) + if fuelContainer then + s_gui:setContainer("Fuel", fuelContainer) + end + + local externalFuelContainer, _, _ = self:getInputs() + if externalFuelContainer then + s_gui:setVisible("FuelContainer", true) + end + + if not sm.game.getEnableFuelConsumption() then + s_gui:setVisible("BackgroundGas", false) + s_gui:setVisible("FuelGrid", false) + end + + s_gui:open() + self.gui = s_gui + end +end + +function ModThruster:server_onCreate() + self.sv_thruster_active = false + + --load the thruster data + self.saved = self.storage:load() + if self.saved == nil then + self.saved = {} + end + if self.saved.gearIdx == nil then + local cur_data = thruster_uuid_data[tostring(self.shape.uuid)] + + self.saved.gearIdx = math.floor(cur_data.gear_count / 2) + end + if self.saved.fuelPoints == nil then + self.saved.fuelPoints = 0 + end + + self.sv_fuel_points = self.saved.fuelPoints + self:server_setGear(self.saved.gearIdx) + + --create the container or reuse the saved one + local s_container = self.interactable:getContainer(0) + if not s_container then + s_container = self.interactable:addContainer(0, 1, 20) + end + + s_container:setFilters({ obj_consumable_gas }) +end + +function ModThruster:server_setGear(gear) + self.saved.gearIdx = gear + self.sv_data_dirty = true + self.sv_storage_dirty = true +end + +function ModThruster:client_getAvailableParentConnectionCount(connectionType) + if bit.band( connectionType, sm.interactable.connectionType.logic ) == sm.interactable.connectionType.logic then + return 1 - #self.interactable:getParents(sm.interactable.connectionType.logic) + end + + if bit.band( connectionType, sm.interactable.connectionType.power ) == sm.interactable.connectionType.power then + return 1 - #self.interactable:getParents(sm.interactable.connectionType.power) + end + + if bit.band( connectionType, sm.interactable.connectionType.gasoline ) == sm.interactable.connectionType.gasoline then + return 1 - #self.interactable:getParents(sm.interactable.connectionType.gasoline) + end + + return 0 +end + +local s_connection_type = sm.interactable.connectionType +function ModThruster:getInputs() + local s_inter = self.interactable + local l_container_inter = s_inter:getParents(s_connection_type.gasoline)[1] + local l_logic = s_inter:getParents(s_connection_type.logic)[1] + local l_driver_seat = s_inter:getParents(s_connection_type.power)[1] + + local l_container = nil + if l_container_inter then + l_container = l_container_inter:getContainer(0) + end + + return l_container, l_logic, l_driver_seat +end + +function ModThruster:client_onOutOfGas() + local l_player = sm.localPlayer.getPlayer() + local l_character = l_player:getCharacter() + + if l_character then + if (self.shape.worldPosition - l_character.worldPosition):length2() < 100 then + sm.gui.displayAlertText("#{INFO_OUT_OF_FUEL}") + end + end +end + +function ModThruster:server_updateFuelStatus() + if self.saved.fuelPoints ~= self.sv_fuel_points then + self.saved.fuelPoints = self.sv_fuel_points + self.sv_fuel_save_timer = 1 + + if self.sv_fuel_points <= 0 then + self.network:sendToClients("client_onOutOfGas") + end + end +end + +function ModThruster:server_onFixedUpdate(dt) + local l_container, l_logic, l_driver_seat = self:getInputs() + local useCreativeFuel = not sm.game.getEnableFuelConsumption() and l_container == nil + + if not l_container or l_container:isEmpty() then + l_container = self.interactable:getContainer(0) + end + + local l_active = false + if l_driver_seat and l_driver_seat.power > 0 then + l_active = true + elseif l_logic and l_logic.active then + l_active = true + end + + local canSpend = false + if self.sv_fuel_points <= 0 then + canSpend = sm.container.canSpend(l_container, obj_consumable_gas, 1) + end + + local is_valid_active = l_active and (self.sv_fuel_points > 0 or canSpend or useCreativeFuel) + if is_valid_active then + local cur_power = self.gears[self.saved.gearIdx + 1].averageForce + sm.physics.applyImpulse(self.shape, sm.vec3.new(0, 0, 0 - cur_power * dt)) + + if not useCreativeFuel then + local fuel_cost = cur_power * 0.1 + self.sv_fuel_points = self.sv_fuel_points - (fuel_cost * dt) + + if self.sv_fuel_points <= 0 then + sm.container.beginTransaction() + sm.container.spend(l_container, obj_consumable_gas, 1, true) + if sm.container.endTransaction() then + self.sv_fuel_points = 10000 + end + end + end + end + + self:server_updateFuelStatus() + + if self.sv_fuel_save_timer ~= nil then + self.sv_fuel_save_timer = self.sv_fuel_save_timer - dt + + if self.sv_fuel_save_timer < 0 then + self.sv_fuel_save_timer = nil + self.sv_storage_dirty = true + end + end + + if self.sv_thruster_active ~= is_valid_active then + self.sv_thruster_active = is_valid_active + self.sv_data_dirty = true + end + + if self.sv_storage_dirty then + self.sv_storage_dirty = false + self.storage:save(self.saved) + end + + if self.sv_data_dirty then + self.sv_data_dirty = false + self.network:setClientData({ self.saved.gearIdx, is_valid_active }) + end +end \ No newline at end of file diff --git a/Scripts/interactable/Locomotion/SmartControl.lua b/Scripts/interactable/Locomotion/SmartControl.lua index 1bd69e8..9c1a11f 100644 --- a/Scripts/interactable/Locomotion/SmartControl.lua +++ b/Scripts/interactable/Locomotion/SmartControl.lua @@ -10,20 +10,31 @@ print("loading SmartControl.lua") SmartControl = class( nil ) SmartControl.maxChildCount = -1 SmartControl.maxParentCount = -1 -SmartControl.connectionInput = sm.interactable.connectionType.power + sm.interactable.connectionType.logic +SmartControl.connectionInput = sm.interactable.connectionType.power + sm.interactable.connectionType.logic + sm.interactable.connectionType.electricity SmartControl.connectionOutput = sm.interactable.connectionType.piston + sm.interactable.connectionType.bearing SmartControl.colorNormal = sm.color.new(0xe54500ff) SmartControl.colorHighlight = sm.color.new(0xff7033ff) SmartControl.poseWeightCount = 1 -function SmartControl.server_onCreate(self) +function SmartControl:server_onCreate() self.last_length = {} + self.delta_length = {} + mp_fuel_initialize(self, obj_consumable_battery, 0.35, sm.interactable.connectionType.electricity) + + local saved_points = self.storage:load() + if saved_points ~= nil then + self.sv_fuel_points = saved_points + end + + self.sv_saved_fuel_points = self.sv_fuel_points end + --smart engine/controller (setangle mode(angle, speed, strength), setspeed mode(speed, strength)) --smart piston/suspension (length, speed , strength -function SmartControl.server_onFixedUpdate(self, dt) - local parents = self.interactable:getParents() +local sc_logic_and_power = bit.bor(sm.interactable.connectionType.logic, sm.interactable.connectionType.power) +function SmartControl:server_onFixedUpdate(dt) + local parents = self.interactable:getParents(sc_logic_and_power) local anglelength = nil local speed = nil @@ -73,9 +84,17 @@ function SmartControl.server_onFixedUpdate(self, dt) if strength then strength = sm.util.clamp(strength, -3.402e+38, 3.402e+38) end if anglelength then anglelength = sm.util.clamp(anglelength, -3.402e+38, 3.402e+38) end if stiffness then stiffness = sm.util.clamp(stiffness, -3.402e+38, 3.402e+38) end - - if logic ~= 0 then + local l_container = mp_fuel_getValidFuelContainer(self) + local can_activate, can_spend_fuel = mp_fuel_canConsumeFuel(self, l_container) + + if not can_activate then + speed = 0 + end + + if logic ~= 0 and can_activate then + local fuel_cost = 0 + local angle = (anglelength ~= nil and math.rad(anglelength) or nil) local rotationspeed = (speed ~= nil and math.rad(speed) or math.rad(0))-- speed 0 by default as to not let it rotate bearing when no inputs local rotationstrength = (strength ~= nil and strength or 10000) @@ -88,24 +107,38 @@ function SmartControl.server_onFixedUpdate(self, dt) local angle1 = math.deg(angle)%360 - (math.deg(angle)%360 > 180 and 360 or 0) local angle2 = (math.deg(v.angle)%360 - (math.deg(v.angle)%360 > 180 and 360 or 0))*(v.reversed and 1 or -1) local extraforce = math.abs(((angle1 - angle2)+180)%360-180)/1000*stiffness + sm.joint.setTargetAngle( v, angle*seat, rotationspeed, rotationstrength*(1+ extraforce) - v.angularVelocity*10) -- change 10 to 1-200 depending on how well dampening oscillations works - - end + end + + local rotation_val = math.abs(v.angularVelocity) * rotationstrength + fuel_cost = fuel_cost + (rotation_val * 0.04) end local length = (anglelength ~= nil and anglelength or 0) local pistonspeed = (speed ~= nil and speed or 15)--default to 15 local pistonstrength = (strength ~= nil and strength or 6666) for k, v in pairs(sm.interactable.getPistons(self.interactable )) do + local v_id = v.id + local old_length = self.delta_length[v_id] or 0 + -- delta length for suspension-ish - if not self.last_length[v.id] then self.last_length[v.id] = v.length end - local extraforce = math.abs(length - (v.length-1))*stiffness/100 + if not self.last_length[v_id] then self.last_length[v_id] = v.length end + if self.delta_length[v_id] ~= v.length then self.delta_length[v_id] = v.length end - local maxImpulse = pistonstrength*(1+ extraforce ) - (v.length-self.last_length[v.id])*10 -- change 10 to 1-200 depending on how well dampening oscillations works + local extraforce = math.abs(length - (v.length-1))*stiffness/100 + local maxImpulse = pistonstrength*(1+ extraforce ) - (v.length-self.last_length[v_id])*10 -- change 10 to 1-200 depending on how well dampening oscillations works maxImpulse = sm.util.clamp(maxImpulse, -3.402e+38, 3.402e+38) + local p_speed = math.abs(old_length - v.length) * 0.1 + fuel_cost = fuel_cost + (pistonspeed * pistonstrength) * p_speed + sm.joint.setTargetLength( v, length*seat, pistonspeed, maxImpulse ) end + + if can_spend_fuel then + mp_fuel_consumeFuelPoints(self, l_container, fuel_cost, dt) + end else local rotationspeed = (speed ~= nil and math.rad(speed) or math.rad(90)) -- if no input speed setting set , give it a 90°/s speed as to be able to reset bearing to 0° local rotationstrength = (strength ~= nil and strength or 10000) @@ -136,4 +169,26 @@ function SmartControl.server_onFixedUpdate(self, dt) sm.joint.setTargetLength( v, 0, pistonspeed, maxImpulse ) end end + + if self.sv_saved_fuel_points ~= self.sv_fuel_points then + self.sv_saved_fuel_points = self.sv_fuel_points + self.sv_fuel_save_timer = 1 + + if self.sv_fuel_points < 0 then + self.network:sendToClients("client_onOutOfFuel") + end + end + + if self.sv_fuel_save_timer ~= nil then + self.sv_fuel_save_timer = self.sv_fuel_save_timer - dt + + if self.sv_fuel_save_timer < 0 then + self.sv_fuel_save_timer = nil + self.storage:save(self.sv_fuel_points) + end + end end + +function SmartControl:client_onOutOfFuel() + mp_fuel_displayOutOfFuelMessage(self, "#{INFO_OUT_OF_ENERGY}") +end \ No newline at end of file diff --git a/Scripts/interactable/Locomotion/SmartThruster.lua b/Scripts/interactable/Locomotion/SmartThruster.lua index 4c9abca..f3ec9cf 100644 --- a/Scripts/interactable/Locomotion/SmartThruster.lua +++ b/Scripts/interactable/Locomotion/SmartThruster.lua @@ -2,6 +2,7 @@ Copyright (c) 2020 Modpack Team Brent Batch#9261 ]]-- + dofile "../../libs/load_libs.lua" print("loading SmartThruster.lua") @@ -10,7 +11,7 @@ print("loading SmartThruster.lua") SmartThruster = class( nil ) SmartThruster.maxParentCount = -1 SmartThruster.maxChildCount = 0 -SmartThruster.connectionInput = sm.interactable.connectionType.power + sm.interactable.connectionType.logic +SmartThruster.connectionInput = sm.interactable.connectionType.power + sm.interactable.connectionType.logic + sm.interactable.connectionType.gasoline SmartThruster.connectionOutput = sm.interactable.connectionType.none SmartThruster.colorNormal = sm.color.new( 0x009999ff ) SmartThruster.colorHighlight = sm.color.new( 0x11B2B2ff ) @@ -18,7 +19,14 @@ SmartThruster.poseWeightCount = 2 function SmartThruster.server_onCreate( self ) + mp_fuel_initialize(self, obj_consumable_gas, 0.35) + + local saved_data = self.storage:load() + if saved_data ~= nil then + self.sv_fuel_points = saved_data + end + self.sv_saved_fuel_points = self.sv_fuel_points end function SmartThruster.server_onRefresh( self ) @@ -26,16 +34,16 @@ function SmartThruster.server_onRefresh( self ) end - +local st_logic_and_power = bit.bor(sm.interactable.connectionType.power, sm.interactable.connectionType.logic) function SmartThruster.server_onFixedUpdate( self, dt ) - local parents = self.interactable:getParents() + local parents = self.interactable:getParents(st_logic_and_power) local power = #parents>0 and 100 or 0 local hasnumber = false local logicinput = 1 for k,v in pairs(parents) do local typeparent = v:getType() - if v:getType() == "scripted" and tostring(v:getShape():getShapeUuid()) ~= "6f2dd83e-bc0d-43f3-8ba5-d5209eb03d07" then + if typeparent == "scripted" and tostring(v:getShape():getShapeUuid()) ~= "6f2dd83e-bc0d-43f3-8ba5-d5209eb03d07" then -- number if not hasnumber then power = 1 end power = power * v.power @@ -51,16 +59,48 @@ function SmartThruster.server_onFixedUpdate( self, dt ) if math.abs(power) >= 3.3*10^38 then -- inf check if power < 0 then power = -3.3*10^38 else power = 3.3*10^38 end end + + local l_container = mp_fuel_getValidFuelContainer(self) + local can_activate, can_consume = mp_fuel_canConsumeFuel(self, l_container) + + if not can_activate then + power = 0 + end mp_updateOutputData(self, power * (logicinput or 1), logicinput > 0) - power = power * logicinput - - if power ~= 0 and math.abs(power) ~= math.huge then + + if can_activate and power ~= 0 and math.abs(power) ~= math.huge then sm.physics.applyImpulse(self.shape, sm.vec3.new(0,0, 0 - power)) + + if can_consume then + mp_fuel_consumeFuelPoints(self, l_container, power, dt) + end + end + + if self.sv_saved_fuel_points ~= self.sv_fuel_points then + self.sv_saved_fuel_points = self.sv_fuel_points + self.sv_fuel_save_timer = 1 + + if self.sv_fuel_points <= 0 then + self.network:sendToClients("client_onOutOfFuel") + end + end + + if self.sv_fuel_save_timer ~= nil then + self.sv_fuel_save_timer = self.sv_fuel_save_timer - dt + + if self.sv_fuel_save_timer < 0 then + self.sv_fuel_save_timer = nil + self.storage:save(self.sv_fuel_points) + end end end +function SmartThruster:client_onOutOfFuel() + mp_fuel_displayOutOfFuelMessage(self) +end + function SmartThruster.client_onCreate(self) self.shootEffect = sm.effect.createEffect( "Thruster - Level 4", self.interactable ) @@ -97,7 +137,8 @@ function SmartThruster.client_onUpdate(self, dt) -- 1 tick delayed vs server but else if self.shootEffect:isPlaying() then - self.shootEffect:stop() end + self.shootEffect:stop() + end end self.interactable:setPoseWeight(0, poseVal0) diff --git a/Scripts/interactable/Locomotion/WASDThruster.lua b/Scripts/interactable/Locomotion/WASDThruster.lua index 957de20..b5584b0 100644 --- a/Scripts/interactable/Locomotion/WASDThruster.lua +++ b/Scripts/interactable/Locomotion/WASDThruster.lua @@ -9,7 +9,7 @@ print("loading WASDThruster.lua") WASDThruster = class( nil ) WASDThruster.maxParentCount = -1 WASDThruster.maxChildCount = 0 -WASDThruster.connectionInput = sm.interactable.connectionType.power + sm.interactable.connectionType.logic +WASDThruster.connectionInput = sm.interactable.connectionType.power + sm.interactable.connectionType.logic + sm.interactable.connectionType.gasoline WASDThruster.connectionOutput = sm.interactable.connectionType.none WASDThruster.colorNormal = sm.color.new( 0x009999ff ) WASDThruster.colorHighlight = sm.color.new( 0x11B2B2ff ) @@ -26,11 +26,21 @@ function WASDThruster.server_init( self ) self.power = 0 self.direction = sm.vec3.new(0,0,1) self.smode = 0 + + mp_fuel_initialize(self, obj_consumable_gas, 0.35) local stored = self.storage:load() - if stored and type(stored)=="number" then - self.smode = stored - 1 + if stored then + local stored_type = type(stored) + if stored_type == "number" then + self.smode = stored - 1 + elseif stored_type == "table" then + self.smode = stored[1] - 1 + self.sv_fuel_points = stored[2] + end end + + self.sv_saved_fuel_points = self.sv_fuel_points end function WASDThruster.server_onRefresh( self ) @@ -38,16 +48,56 @@ function WASDThruster.server_onRefresh( self ) end function WASDThruster.server_onFixedUpdate( self, dt ) + local l_container = mp_fuel_getValidFuelContainer(self) + local can_activate, can_consume = mp_fuel_canConsumeFuel(self, l_container) + if self.interactable.power ~= self.power then self.interactable:setPower(self.power) end - if self.power > 0 and math.abs(self.power) ~= math.huge then + + if can_activate and self.power > 0 and math.abs(self.power) ~= math.huge then sm.physics.applyImpulse(self.shape, self.direction*self.power*-1) - --print(self.direction) + + if can_consume then + mp_fuel_consumeFuelPoints(self, l_container, self.power, dt) + end + end + + if self.sv_saved_can_activate ~= can_activate then + self.sv_saved_can_activate = can_activate + self.network:setClientData(can_activate) + end + + if self.sv_saved_fuel_points ~= self.sv_fuel_points then --update fuel status + self.sv_saved_fuel_points = self.sv_fuel_points + self.sv_fuel_save_timer = 1 + + if self.sv_fuel_points <= 0 then + self.network:sendToClients("client_onOutOfFuel") + end + end + + if self.sv_fuel_save_timer ~= nil then + self.sv_fuel_save_timer = self.sv_fuel_save_timer - dt + + if self.sv_fuel_save_timer < 0 then + self.sv_fuel_save_timer = nil + self.storage:save({ self.smode+1, self.sv_fuel_points }) + end end end +function WASDThruster:client_onClientDataUpdate(params) + self.cl_can_activate = params +end + + +function WASDThruster:client_onOutOfFuel() + mp_fuel_displayOutOfFuelMessage(self) +end + + function WASDThruster.client_onCreate(self) self.shootEffect = sm.effect.createEffect( "Thruster - Level 2", self.interactable ) self.shootEffect:setOffsetPosition(sm.vec3.zero()) @@ -59,7 +109,7 @@ function WASDThruster.client_onCreate(self) self.currentVPose = 0.5 self.mode = 0 self.network:sendToServer("server_requestmode") - self.modes = {"wasd", "ws reversed", "only WS", "only AD"} + self.modes = {"WASD", "WS Reversed", "Only WS", "Only AD"} self.interactable:setAnimEnabled( "animY", true ) self.interactable:setAnimEnabled( "animX", true ) @@ -67,38 +117,51 @@ end function WASDThruster.client_onDestroy(self) - self.shootEffect:stop() + self.shootEffect:stopImmediate() end function WASDThruster.client_onInteract(self, character, lookAt) if not lookAt or character:getLockingInteractable() then return end self.network:sendToServer("server_changemode", character:isCrouching()) end + function WASDThruster.server_changemode(self, crouch) self.smode = (self.smode + (crouch and -1 or 1))%4 - self.storage:save(self.smode+1) + self.storage:save({ self.smode+1, self.sv_saved_fuel_points }) self.network:sendToClients("client_mode", {self.smode, true}) end + function WASDThruster.server_requestmode(self) self.network:sendToClients("client_mode", {self.smode}) end + function WASDThruster.client_mode(self, mode) if mode[2] then sm.audio.play("ConnectTool - Rotate", self.shape:getWorldPosition()) end self.mode = mode[1] end + +local default_hypertext = "%s
" function WASDThruster.client_canInteract(self) - local _useKey = sm.gui.getKeyBinding("Use") - local _crawlKey = sm.gui.getKeyBinding("Crawl") - sm.gui.setInteractionText("Press", _useKey, " / ", _crawlKey.." + ".._useKey, "to change mode") - sm.gui.setInteractionText( "current mode: ".. self.modes[self.mode+1]) + local use_key = sm.gui.getKeyBinding("Use") + local crawl_key = sm.gui.getKeyBinding("Crawl") + + local use_hyper = default_hypertext:format(use_key) + local use_and_crawl_hyper = default_hypertext:format(crawl_key.." + "..use_key) + + sm.gui.setInteractionText("Press", use_hyper, "or", use_and_crawl_hyper, "to change mode") + + local cur_mode_hyper = default_hypertext:format("Mode: "..self.modes[self.mode+1]) + sm.gui.setInteractionText("", cur_mode_hyper) + return true end +local wasdt_logic_and_power = bit.bor(sm.interactable.connectionType.logic, sm.interactable.connectionType.power) function WASDThruster.client_onFixedUpdate(self, dt) - local parents = self.interactable:getParents() + local parents = self.interactable:getParents(wasdt_logic_and_power) local power = #parents>0 and 100 or 0 local hasnumber = false local logicinput = 1 @@ -194,8 +257,17 @@ function WASDThruster.client_onFixedUpdate(self, dt) if ws or ad then self.currentVPose = 0.5 end -- -1 to 1 => 0 to 1 if ad then self.currentHPose = (ad+1)/2 end -- -1 to 1 => 0 to 1 end - self.power = power * logicinput * canfire - if math.abs(self.power) == math.huge or self.power ~= self.power then self.power = 0 end + + --check if the thruster is allowed to have any power + if self.cl_can_activate then + self.power = power * logicinput * canfire + else + self.power = 0 + end + + if math.abs(self.power) == math.huge or self.power ~= self.power then + self.power = 0 + end self.interactable:setUvFrameIndex(self.mode) @@ -213,12 +285,15 @@ function WASDThruster.client_onFixedUpdate(self, dt) local worldRot = sm.vec3.getRotation( getLocal(self.shape,sm.shape.getUp(self.shape)),self.direction) self.shootEffect:setOffsetRotation(worldRot) --self.shootEffect:setOffsetPosition((-sm.vec3.new(0,0,1.25)+self.direction)*0.36) --old calculations + if self.power > 0 then if not self.shootEffect:isPlaying() then - self.shootEffect:start() end + self.shootEffect:start() + end else if self.shootEffect:isPlaying() then - self.shootEffect:stop() end + self.shootEffect:stop() + end end diff --git a/Scripts/interactable/NumberLogic/AsciiBlock.lua b/Scripts/interactable/NumberLogic/AsciiBlock.lua index 7a14c74..ae3bab8 100644 --- a/Scripts/interactable/NumberLogic/AsciiBlock.lua +++ b/Scripts/interactable/NumberLogic/AsciiBlock.lua @@ -340,11 +340,16 @@ function AsciiBlock.client_onInteract(self, character, lookAt) self.network:sendToServer("server_changemode", character:isCrouching()) end +local default_hypertext = "%s
" function AsciiBlock.client_canInteract(self) - local _useKey = sm.gui.getKeyBinding("Use") - local _crawlKey = sm.gui.getKeyBinding("Crawl") - sm.gui.setInteractionText("", _useKey, "to cycle forward") - sm.gui.setInteractionText("", _crawlKey.." + ".._useKey, "to cycle backwards") + local use_key = sm.gui.getKeyBinding("Use") + local crawl_key = sm.gui.getKeyBinding("Crawl") + + local use_hyper = default_hypertext:format(use_key) + local crawl_and_use_hyper = default_hypertext:format(crawl_key.." + "..use_key) + + sm.gui.setInteractionText("Press", use_hyper, "or", crawl_and_use_hyper, "to change") + return true end function AsciiBlock.client_playsound(self, sound) diff --git a/Scripts/interactable/NumberLogic/CounterBlock.lua b/Scripts/interactable/NumberLogic/CounterBlock.lua index ed87a83..802dae3 100644 --- a/Scripts/interactable/NumberLogic/CounterBlock.lua +++ b/Scripts/interactable/NumberLogic/CounterBlock.lua @@ -106,36 +106,142 @@ function CounterBlock.server_onFixedUpdate( self, dt ) mp_updateOutputData(self, self.power, self.power > 0) end +function CounterBlock.server_setNewValue(self, value, caller) + self.power = value + + self.network:sendToClient(caller, "client_playSound", 2) +end + +function CounterBlock.server_receiveValue(self, value, caller) + self.power = self.power + value + + self.network:sendToClient(caller, "client_playSound", 2) +end function CounterBlock.server_reset(self) if self.power > 0 then - self.network:sendToClients("client_resetSound") + self.network:sendToClients("client_playSound", 1) end self.power = 0 end -function CounterBlock.client_resetSound(self) - sm.audio.play("GUI Item drag", self.shape:getWorldPosition()) +local sound_id_table = +{ + [1] = "GUI Item drag", + [2] = "GUI Item released" +} + +function CounterBlock.client_playSound(self, sound_id) + local sound_id_table = sound_id_table[sound_id] + + sm.audio.play(sound_id_table, self.shape:getWorldPosition()) end function CounterBlock.client_onInteract(self, character, lookAt) if not lookAt or character:getLockingInteractable() then return end + self.network:sendToServer("server_reset") end +function CounterBlock.client_onTextChangedCallback(self, widget, text) + local converted_text = tonumber(text) --will be nill if the input is invalid + local is_valid = (converted_text ~= nil) + + self.counter_gui_input = text + + local count_gui = self.counter_gui + count_gui:setVisible("ValueError", not is_valid) + + count_gui:setVisible("IncrementWith", is_valid) + count_gui:setVisible("DecrementWith", is_valid) + count_gui:setVisible("SaveWrittenVal", is_valid) +end + +function CounterBlock.client_onGuiCloseCallback(self) + local count_gui = self.counter_gui + if count_gui and sm.exists(count_gui) then + if count_gui:isActive() then + count_gui:close() + end + + count_gui:destroy() + end + + self.counter_gui_input = nil + self.counter_gui = nil +end + +function CounterBlock.client_gui_updateSavedValueText(self) + self.counter_gui:setText("SavedValue", "Saved Value: #ffff00"..tostring(self.interactable.power).."#ffffff") +end + +function CounterBlock.client_gui_changeSavedValue(self, widget) + local is_decrement = (widget:sub(0, 1) == "D") + + local cur_changer = tonumber(self.counter_gui_input) + if cur_changer ~= nil then + if is_decrement then + cur_changer = -cur_changer + end + + self.network:sendToServer("server_receiveValue", cur_changer) + end +end + +function CounterBlock.client_gui_saveWrittenValue(self) + local cur_value = tonumber(self.counter_gui_input) + if cur_value ~= nil then + self.network:sendToServer("server_setNewValue", cur_value) + end +end + +function CounterBlock.client_onTinker(self, character, lookAt) + if not lookAt or character:getLockingInteractable() then return end + + local count_gui = sm.gui.createGuiFromLayout("$CONTENT_DATA/Gui/Layouts/CounterBlockGui.layout", false, { backgroundAlpha = 0.5 }) + + self.counter_gui_input = tostring(self.interactable.power) + + count_gui:setText("SavedValue", "Saved Value: #ffff00"..tostring(self.interactable.power).."#ffffff") + count_gui:setText("ValueInput", self.counter_gui_input) + + count_gui:setButtonCallback("IncrementWith", "client_gui_changeSavedValue") + count_gui:setButtonCallback("DecrementWith", "client_gui_changeSavedValue") + count_gui:setButtonCallback("SaveWrittenVal", "client_gui_saveWrittenValue") + + count_gui:setTextChangedCallback("ValueInput", "client_onTextChangedCallback") + count_gui:setOnCloseCallback("client_onGuiCloseCallback") + + count_gui:open() + + self.counter_gui = count_gui +end function CounterBlock.client_onCreate(self, dt) self.frameindex = 0 self.lastpower = 0 end +function CounterBlock.client_onDestroy(self) + self:client_onGuiCloseCallback() +end + function CounterBlock.client_canInteract(self) - local _useKey = sm.gui.getKeyBinding("Use") - sm.gui.setInteractionText("Press", _useKey, "to reset counter") + local use_key = sm.gui.getKeyBinding("Use", true) + local tinker_key = sm.gui.getKeyBinding("Tinker", true) + + sm.gui.setInteractionText("Press", use_key, "to reset counter") + sm.gui.setInteractionText("Press", tinker_key, "to open gui") + return true end function CounterBlock.client_onFixedUpdate(self, dt) + local count_gui = self.counter_gui + if count_gui then + self:client_gui_updateSavedValueText() + end + local power = self.interactable.power if self.powerSkip == power then return end -- more performance (only update uv if power changes) diff --git a/Scripts/interactable/NumberLogic/MathBlock.lua b/Scripts/interactable/NumberLogic/MathBlock.lua index 3cabb1c..6c0915f 100644 --- a/Scripts/interactable/NumberLogic/MathBlock.lua +++ b/Scripts/interactable/NumberLogic/MathBlock.lua @@ -879,12 +879,32 @@ function MathBlock.client_onCreate(self) self.network:sendToServer("sv_senduvtoclient") end +function MathBlock.client_onDestroy(self) + self:client_onGuiDestroyCallback() +end + +function MathBlock.client_onGuiDestroyCallback(self) + local s_gui = self.gui + if s_gui and sm.exists(s_gui) then + if s_gui:isActive() then + s_gui:close() + end + + s_gui:destroy() + end + + self.gui = nil +end + function MathBlock.client_onInteract(self, character, lookAt) if lookAt == true then - self.gui = sm.gui.createGuiFromLayout('$MOD_DATA/Gui/Layouts/MathBlock.layout') + self.gui = sm.gui.createGuiFromLayout("$MOD_DATA/Gui/Layouts/MathBlock.layout", false, { backgroundAlpha = 0.5 }) + self.gui:setOnCloseCallback("client_onGuiDestroyCallback") + for i = 0, 23 do self.gui:setButtonCallback( "Operation" .. tostring( i ), "cl_onModeButtonClick" ) end + for i = 1, 3 do self.gui:setButtonCallback( "Page" .. tostring( i ), "cl_onPageButtonClick" ) end @@ -972,8 +992,9 @@ function MathBlock.cl_setMode(self, data) end function MathBlock.client_canInteract(self) - local _useKey = sm.gui.getKeyBinding("Use") - sm.gui.setInteractionText("Press", _useKey, " to select a function") + local use_key = sm.gui.getKeyBinding("Use", true) + sm.gui.setInteractionText("Press", use_key, "to select a function") + return true end diff --git a/Scripts/interactable/NumberOut/SmartSensor.lua b/Scripts/interactable/NumberOut/SmartSensor.lua index 59c7fdd..fd32edd 100644 --- a/Scripts/interactable/NumberOut/SmartSensor.lua +++ b/Scripts/interactable/NumberOut/SmartSensor.lua @@ -197,12 +197,32 @@ function SmartSensor.client_onCreate(self) self.network:sendToServer("sv_requestMode") end +function SmartSensor.client_onDestroy(self) + self:client_onGuiDestroyCallback() +end + +function SmartSensor.client_onGuiDestroyCallback(self) + local s_gui = self.gui + if s_gui and sm.exists(s_gui) then + if s_gui:isActive() then + s_gui:close() + end + + s_gui:destroy() + end + + self.gui = nil +end + function SmartSensor.client_onInteract(self, character, lookAt) if lookAt == true then - self.gui = sm.gui.createGuiFromLayout('$MOD_DATA/Gui/Layouts/SmartSensor.layout') + self.gui = sm.gui.createGuiFromLayout("$MOD_DATA/Gui/Layouts/SmartSensor.layout", false, { backgroundAlpha = 0.5 }) + self.gui:setOnCloseCallback("client_onGuiDestroyCallback") + for i = 0, 5 do self.gui:setButtonCallback( "Operation" .. tostring( i ), "cl_onModeButtonClick" ) end + self:cl_drawButtons() self.gui:open() end @@ -227,8 +247,9 @@ function SmartSensor.cl_drawButtons(self) end function SmartSensor.client_canInteract(self, character, lookAt) - local _useKey = sm.gui.getKeyBinding("Use") - sm.gui.setInteractionText("Press", _useKey, "to change mode") + local use_key = sm.gui.getKeyBinding("Use", true) + sm.gui.setInteractionText("Press", use_key, "to select a sensor mode") + return true end diff --git a/Scripts/interactable/Orientation/Orienter.lua b/Scripts/interactable/Orientation/Orienter.lua index a1106af..0829f36 100644 --- a/Scripts/interactable/Orientation/Orienter.lua +++ b/Scripts/interactable/Orientation/Orienter.lua @@ -161,6 +161,10 @@ function Orienter.client_onCreate(self) self.network:sendToServer("sv_sendModeToClient") end +function Orienter.client_onDestroy(self) + self:client_onGuiDestroyCallback() +end + function Orienter.sv_sendModeToClient(self) local _UvIndex = self.modetable[self.mode].savevalue - 1 self.network:sendToClients("cl_setMode", { uvIndex = _UvIndex, mode = self.mode }) @@ -182,9 +186,24 @@ local _UnitTable = { 'UnitsTotebot', 'UnitsWoc', 'UnitsGlowworm' } +function Orienter.client_onGuiDestroyCallback(self) + local s_gui = self.gui + if s_gui and sm.exists(s_gui) then + if s_gui:isActive() then + s_gui:close() + end + + s_gui:destroy() + end + + self.gui = nil +end + function Orienter.client_onInteract(self, character, lookAt) if lookAt == true then - self.gui = sm.gui.createGuiFromLayout('$MOD_DATA/Gui/Layouts/Orienter.layout') + self.gui = sm.gui.createGuiFromLayout("$MOD_DATA/Gui/Layouts/Orienter.layout", false, { backgroundAlpha = 0.5 }) + self.gui:setOnCloseCallback("client_onGuiDestroyCallback") + for _, buttonName in pairs(targetTable) do self.gui:setButtonCallback(buttonName, "cl_onTargetButtonClick") end @@ -367,8 +386,9 @@ function Orienter.sv_changeMode(self, params) end function Orienter.client_canInteract(self) - local _useKey = sm.gui.getKeyBinding("Use") - sm.gui.setInteractionText("Press", _useKey, " to change the mode") + local use_key = sm.gui.getKeyBinding("Use", true) + sm.gui.setInteractionText("Press", use_key, "to select an orient mode") + return true end diff --git a/Scripts/interactable/Orientation/XOMeter.lua b/Scripts/interactable/Orientation/XOMeter.lua index 2904d4a..ea29590 100644 --- a/Scripts/interactable/Orientation/XOMeter.lua +++ b/Scripts/interactable/Orientation/XOMeter.lua @@ -340,9 +340,9 @@ end function XOMeter.client_canInteract(self) - local _useKey = sm.gui.getKeyBinding("Use") - local _crawlKey = sm.gui.getKeyBinding("Crawl") - sm.gui.setInteractionText("Press", _useKey, " to change the meter mode") + local use_key = sm.gui.getKeyBinding("Use", true) + sm.gui.setInteractionText("Press", use_key, "to select the meter mode") + return true end @@ -358,6 +358,10 @@ function XOMeter.client_onCreate(self) self.network:sendToServer("server_sendModeToClient") end +function XOMeter.client_onDestroy(self) + self:client_onGuiDestroyCallback() +end + function XOMeter.client_onFixedUpdate(self, dt) local mode = self.modetable[self.mode_client] @@ -439,12 +443,28 @@ function XOMeter.client_onFixedUpdate(self, dt) end end +function XOMeter.client_onGuiDestroyCallback(self) + local s_gui = self.gui + if s_gui and sm.exists(s_gui) then + if s_gui:isActive() then + s_gui:close() + end + + s_gui:destroy() + end + + self.gui = nil +end + function XOMeter.client_onInteract(self, character, lookAt) if lookAt == true then - self.gui = sm.gui.createGuiFromLayout('$MOD_DATA/Gui/Layouts/XOMeter.layout') + self.gui = sm.gui.createGuiFromLayout("$MOD_DATA/Gui/Layouts/XOMeter.layout", false, { backgroundAlpha = 0.5 }) + self.gui:setOnCloseCallback("client_onGuiDestroyCallback") + for i = 0, 10 do self.gui:setButtonCallback( "Operation" .. tostring( i ), "cl_onModeButtonClick" ) end + self:cl_drawButtons() self.gui:open() end diff --git a/Scripts/libs/debugger.lua b/Scripts/libs/debugger.lua index b5f6598..989afc8 100644 --- a/Scripts/libs/debugger.lua +++ b/Scripts/libs/debugger.lua @@ -1,9 +1,7 @@ -if _loadedModpackDebugger then - return -end +if _loadedModpackDebugger then return end _loadedModpackDebugger = true local printO = print function print(...) -- fancy print by TechnologicNick - printO("[" .. sm.game.getCurrentTick() .. "]", sm.isServerMode() and "[Server]" or "[Client]", ...) -end + printO(sm.isServerMode() and "[Server]" or "[Client]", ...) +end \ No newline at end of file diff --git a/Scripts/libs/fuel_consumption_manager.lua b/Scripts/libs/fuel_consumption_manager.lua new file mode 100644 index 0000000..53a0fd1 --- /dev/null +++ b/Scripts/libs/fuel_consumption_manager.lua @@ -0,0 +1,58 @@ +dofile("$SURVIVAL_DATA/Scripts/game/survival_items.lua") --to get the uuids of consumable items + +local _sm_getEnableFuelConsumption = sm.game.getEnableFuelConsumption + +function mp_fuel_initialize(self, fuel_uuid, fuel_multiplier, connection_type) + self.sv_fuel_points = 0 + self.sv_fuel_multiplier = fuel_multiplier + self.sv_fuel_uuid = fuel_uuid + self.sv_fuel_connect_type = connection_type or sm.interactable.connectionType.gasoline +end + +function mp_fuel_consumeFuelPoints(self, container, power, dt) + local abs_power = math.abs(power) * self.sv_fuel_multiplier + self.sv_fuel_points = self.sv_fuel_points - (abs_power * dt) --calculate fuel consumption + + if self.sv_fuel_points <= 0 then + sm.container.beginTransaction() + sm.container.spend(container, self.sv_fuel_uuid, 1, true) + if sm.container.endTransaction() then + self.sv_fuel_points = 10000 + end + end +end + +function mp_fuel_getValidFuelContainer(self) + local parents = self.interactable:getParents(self.sv_fuel_connect_type) + for k, v in pairs(parents) do + local gas_container = v:getContainer() + + if gas_container ~= nil then + return gas_container + end + end + + return nil +end + +function mp_fuel_canConsumeFuel(self, container) + local useCreativeFuel = not _sm_getEnableFuelConsumption() and container == nil + local canSpend = false + if self.sv_fuel_points <= 0 and container then + canSpend = sm.container.canSpend(container, self.sv_fuel_uuid, 1) + end + + local is_valid_active = (self.sv_fuel_points > 0 or canSpend or useCreativeFuel) + return is_valid_active, not useCreativeFuel +end + +function mp_fuel_displayOutOfFuelMessage(self, custom_message) + local l_player = sm.localPlayer.getPlayer() + local l_character = l_player:getCharacter() + + if l_character then + if (self.shape.worldPosition - l_character.worldPosition):length2() < 100 then + sm.gui.displayAlertText(custom_message or "#{INFO_OUT_OF_FUEL}") + end + end +end \ No newline at end of file diff --git a/Scripts/libs/game_improvements/interactable.lua b/Scripts/libs/game_improvements/interactable.lua index 9c890d2..6d5f3a7 100644 --- a/Scripts/libs/game_improvements/interactable.lua +++ b/Scripts/libs/game_improvements/interactable.lua @@ -4,28 +4,30 @@ __InteractableImprovements_Loaded = true -- server: local values = {} -- <