├── LICENSE
├── CHANGELOG.md
├── README.md
└── MoreSuits.cs
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 x753
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v1.5.1
2 | - Deleted duplicate DLL file
3 |
4 | ## v1.5.0
5 | - Added jump audio support thanks to XuXiaolan
6 |
7 | ## v1.4.5
8 | - Added support for placing advanced.json files in the config folder (thanks darmuh)
9 | - .json files in the BepInEx/config/MoreSuits folder will be used in place of those in the advanced folder
10 |
11 | ## v1.4.4
12 | - Moved Default.png into the Advanced folder so it doesn't overwrite the base suit
13 |
14 | ## v1.4.3
15 | - Reverted a change to how suits were sorted
16 |
17 | ## v1.4.2
18 | - Fixed an issue where suits took up more memory than necessary
19 | - Added a config option to unlock all suits, so you won't have to buy them from the store
20 |
21 | ## v1.4.1
22 | - Fixed a bug when using LethalFashion
23 | - Added material support thanks to ViViKo
24 |
25 | ## v1.4.0
26 | - Bugfixes for adding suits to the shop
27 | - Added a config option (on by default) to position your suits closer together so you can have up to 20 fit on the rack
28 | - Added several new advanced options: "DISABLEKEYWORD", "SHADERPASS", "DISABLESHADERPASS", "SHADER"
29 |
30 | ## v1.3.3
31 | - Added a BepInEx config file so you can exclude certain suits from being loaded or ignore !less-suits.txt\
32 |
33 | ## v1.3.0
34 | - More Suits can now be used as a library for other suit mods
35 |
36 | Older Versions
37 |
38 | ## v1.2.1
39 | - Fixed suits being in a different order on the rack for each player
40 |
41 | ## v1.2.0 Suits in Rotating Shop
42 | - Added support for adding suits to the store rotation
43 |
44 | ## v1.1.0 More suits!
45 | - Added new suits by Graelyth and Curt
46 | - Added support for advanced features (normal maps, emission, etc)
47 |
48 | ## v1.0.0 Release 😎
49 | - Release
50 |
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # More Suits
2 | ### Adds more suits to choose from, and can be used as a library to load your own suits!
3 |
4 | ## Instructions
5 | Place the ```x753-More_Suits-X.X.X``` folder in your ```BepInEx/Plugins``` folder. Make sure the ```moresuits``` folder is in the same folder as ```MoreSuits.dll```.
6 |
7 | ## Config File
8 | After launching the game with the mod once, a config file is generated. In this file you can disable individual suits from being loaded, as well as ignore any ```!less-suits.txt``` file and attempt to load all suits (which is useful if you have another mod that helps manage lots of suits).
9 |
10 | ## Customize
11 | You can add .png files to the ```moresuits``` folder to add new suits as long as both the host and clients have the same files.
12 |
13 | ## Advanced
14 | You can add a .json file in the ```advanced``` folder with the same name as your .png file in the ```moresuits``` folder to enable additional features like emission. Place additional texture maps in the ```advanced``` folder.
15 |
16 | For a list of supported features, see:
17 | https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@14.0/manual/Lit-Shader.html
18 |
19 | ## Add Suits to Store
20 | Add a "PRICE" key to your advanced .json to put a suit in the store rotation. See ```Glow.json``` for an example of adding a suit with emission that must be purchased from the store. If you want to set a custom price for a suit from another mod in your modpack, create a new .json in the BepInEx/config/MoreSuitsConfig folder.
21 |
22 | ## Making your own More Suits mod
23 | Upload your own package with a ```BepInEx/plugins/moresuits``` folder in it (do not include the MoreSuits.dll file) and add ```x753-More_Suits-1.5.1``` as a dependency, and this mod will automatically load your .png files as suits. If you don't want some or all of the suits that originally come with my mod, adjust the config file ```BepInEx\config\x753.More_Suits.cfg```. Include a ```!less-suits.txt``` file in your ```moresuits``` folder to disable all the default suits that come with this mod.
--------------------------------------------------------------------------------
/MoreSuits.cs:
--------------------------------------------------------------------------------
1 | using BepInEx;
2 | using BepInEx.Configuration;
3 | using HarmonyLib;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Reflection;
9 | using UnityEngine;
10 |
11 | namespace MoreSuits
12 | {
13 | [BepInPlugin(modGUID, modName, modVersion)]
14 | public class MoreSuitsMod : BaseUnityPlugin
15 | {
16 | private const string modGUID = "x753.More_Suits";
17 | private const string modName = "More Suits";
18 | private const string modVersion = "1.5.0";
19 |
20 | private readonly Harmony harmony = new Harmony(modGUID);
21 |
22 | private static MoreSuitsMod Instance;
23 |
24 | public static bool SuitsAdded = false;
25 |
26 | public static string DisabledSuits;
27 | public static bool LoadAllSuits;
28 | public static bool MakeSuitsFitOnRack;
29 | public static bool UnlockAll;
30 | public static int MaxSuits;
31 |
32 | public static List customMaterials = new List();
33 | public static List customAudioClips = new List();
34 |
35 | private void Awake()
36 | {
37 | if (Instance == null)
38 | {
39 | Instance = this;
40 | }
41 |
42 | DisabledSuits = Config.Bind("General", "Disabled Suit List", "UglySuit751.png,UglySuit752.png,UglySuit753.png", "Comma-separated list of suits that shouldn't be loaded").Value;
43 | LoadAllSuits = Config.Bind("General", "Ignore !less-suits.txt", false, "If true, ignores the !less-suits.txt file and will attempt to load every suit, except those in the disabled list. This should be true if you're not worried about having too many suits.").Value;
44 | MakeSuitsFitOnRack = Config.Bind("General", "Make Suits Fit on Rack", true, "If true, squishes the suits together so more can fit on the rack.").Value;
45 | UnlockAll = Config.Bind("General", "Unlock All Suits", false, "If true, unlocks all custom suits that would normally be sold in the shop.").Value;
46 | MaxSuits = Config.Bind("General", "Max Suits", 100, "The maximum number of suits to load. If you have more, some will be ignored.").Value;
47 |
48 | harmony.PatchAll();
49 | Logger.LogInfo($"Plugin {modName} is loaded!");
50 | }
51 |
52 | [HarmonyPatch(typeof(StartOfRound))]
53 | internal class StartOfRoundPatch
54 | {
55 | [HarmonyPatch("Start")]
56 | [HarmonyPrefix]
57 | static void StartPatch(ref StartOfRound __instance)
58 | {
59 | try
60 | {
61 | if (!SuitsAdded) // we only need to add the new suits to the unlockables list once per game launch
62 | {
63 | int originalUnlockablesCount = __instance.unlockablesList.unlockables.Count;
64 | UnlockableItem originalSuit = new UnlockableItem();
65 |
66 | int addedSuitCount = 0;
67 | for (int i = 0; i < __instance.unlockablesList.unlockables.Count; i++)
68 | {
69 | UnlockableItem unlockableItem = __instance.unlockablesList.unlockables[i];
70 |
71 | if (unlockableItem.suitMaterial != null && unlockableItem.alreadyUnlocked) // find the default suit to use as a base
72 | {
73 | originalSuit = unlockableItem;
74 |
75 | // Get all .png files from all folders named moresuits in the BepInEx/plugins folder
76 | List suitsFolderPaths = Directory.GetDirectories(Paths.PluginPath, "moresuits", SearchOption.AllDirectories).ToList();
77 | List texturePaths = new List();
78 | List assetPaths = new List();
79 | List disabledSuits = DisabledSuits.ToLower().Replace(".png", "").Split(',').ToList();
80 | List disabledDefaultSuits = new List();
81 |
82 | // Check through each moresuits folder for a text file called !less-suits.txt, which signals not to load any of the original suits that come with this mod
83 | if (!LoadAllSuits)
84 | {
85 | foreach (string suitsFolderPath in suitsFolderPaths)
86 | {
87 | if (File.Exists(Path.Combine(suitsFolderPath, "!less-suits.txt")))
88 | {
89 | string[] defaultSuits = { "glow", "kirby", "knuckles", "luigi", "mario", "minion", "skeleton", "slayer", "smile" };
90 | disabledDefaultSuits.AddRange(defaultSuits); // add every default suit in the mod to the disabled suits list
91 | break;
92 | }
93 | }
94 | }
95 |
96 | foreach (string suitsFolderPath in suitsFolderPaths)
97 | {
98 | if (suitsFolderPath != "")
99 | {
100 | string[] pngFiles = Directory.GetFiles(suitsFolderPath, "*.png");
101 | string[] matBundles = Directory.GetFiles(suitsFolderPath, "*.matbundle", SearchOption.AllDirectories); // legacy bundle file extension
102 | string[] suitBundles = Directory.GetFiles(suitsFolderPath, "*.suitbundle", SearchOption.AllDirectories);
103 |
104 | texturePaths.AddRange(pngFiles);
105 | assetPaths.AddRange(matBundles);
106 | assetPaths.AddRange(suitBundles);
107 | }
108 | }
109 |
110 | assetPaths.Sort();
111 | texturePaths.Sort();
112 |
113 | //assetPaths = assetPaths.OrderBy(Path.GetFileNameWithoutExtension).ThenBy(p => p).ToList();
114 | //texturePaths = texturePaths.OrderBy(Path.GetFileNameWithoutExtension).ThenBy(p => p).ToList();
115 |
116 | Dictionary audioClips = new Dictionary();
117 |
118 | try
119 | {
120 | foreach (string assetPath in assetPaths)
121 | {
122 | AssetBundle assetBundle = AssetBundle.LoadFromFile(assetPath);
123 | UnityEngine.Object[] assets = assetBundle.LoadAllAssets();
124 |
125 | foreach (UnityEngine.Object asset in assets)
126 | {
127 | if (asset is Material)
128 | {
129 | Material material = (Material)asset;
130 | customMaterials.Add(material);
131 | }
132 | if (asset is AudioClip)
133 | {
134 | AudioClip audioClip = (AudioClip)asset;
135 | customAudioClips.Add(audioClip);
136 | }
137 | }
138 | }
139 | }
140 | catch (Exception ex)
141 | {
142 | Debug.Log("Something went wrong with More Suits! Could not load materials from asset bundle(s). Error: " + ex);
143 | }
144 |
145 | // Create new suits for each .png
146 | foreach (string texturePath in texturePaths)
147 | {
148 | // skip each suit that is in the disabled suits list
149 | if (disabledSuits.Contains(Path.GetFileNameWithoutExtension(texturePath).ToLower())) { continue; }
150 | string originalMoreSuitsPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
151 | if (disabledDefaultSuits.Contains(Path.GetFileNameWithoutExtension(texturePath).ToLower()) && texturePath.Contains(originalMoreSuitsPath)) { continue; }
152 |
153 | UnlockableItem newSuit;
154 | Material newMaterial;
155 |
156 | if (Path.GetFileNameWithoutExtension(texturePath).ToLower() == "default")
157 | {
158 | newSuit = originalSuit;
159 | newMaterial = newSuit.suitMaterial;
160 | }
161 | else
162 | {
163 | // Serialize and deserialize to create a deep copy of the original suit item
164 | newSuit = JsonUtility.FromJson(JsonUtility.ToJson(originalSuit));
165 |
166 | newMaterial = Instantiate(newSuit.suitMaterial);
167 | }
168 |
169 | byte[] fileData = File.ReadAllBytes(texturePath);
170 | Texture2D texture = new Texture2D(2, 2);
171 | texture.LoadImage(fileData);
172 |
173 | texture.Apply(true, true);
174 |
175 | newMaterial.mainTexture = texture;
176 |
177 | newSuit.unlockableName = Path.GetFileNameWithoutExtension(texturePath);
178 |
179 | // Optional modification of other properties like normal maps, emission, etc
180 | // https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@14.0/manual/Lit-Shader.html
181 | try
182 | {
183 | string advancedJsonPath = Path.Combine(Path.GetDirectoryName(texturePath), "advanced", newSuit.unlockableName + ".json");
184 | string configJsonPath = Path.Combine(Path.GetDirectoryName(Paths.ConfigPath), "config\\MoreSuitsConfig", newSuit.unlockableName + ".json");
185 |
186 | if (File.Exists(configJsonPath))
187 | {
188 | Instance.Logger.LogInfo($"Utilizing [ {configJsonPath} ] for suit - {newSuit.unlockableName}!");
189 | advancedJsonPath = configJsonPath;
190 | }
191 |
192 | if (File.Exists(advancedJsonPath))
193 | {
194 | string[] lines = File.ReadAllLines(advancedJsonPath);
195 |
196 | foreach (string line in lines)
197 | {
198 | string[] keyValue = line.Trim().Split(':');
199 | if (keyValue.Length == 2)
200 | {
201 | string keyData = keyValue[0].Trim('"', ' ', ',');
202 | string valueData = keyValue[1].Trim('"', ' ', ',');
203 |
204 | if (valueData.Contains(".png"))
205 | {
206 | string advancedTexturePath = Path.Combine(Path.GetDirectoryName(texturePath), "advanced", valueData);
207 | byte[] advancedTextureData = File.ReadAllBytes(advancedTexturePath);
208 | Texture2D advancedTexture = new Texture2D(2, 2);
209 | advancedTexture.LoadImage(advancedTextureData);
210 |
211 | advancedTexture.Apply(true, true);
212 |
213 | newMaterial.SetTexture(keyData, advancedTexture);
214 | }
215 | else if (keyData == "PRICE" && int.TryParse(valueData, out int intValue)) // If the advanced json has a price, set it up so it rotates into the shop
216 | {
217 | try
218 | {
219 | if(!UnlockAll)
220 | newSuit = AddToRotatingShop(newSuit, intValue, __instance.unlockablesList.unlockables.Count);
221 | }
222 | catch (Exception ex)
223 | {
224 | Debug.Log("Something went wrong with More Suits! Could not add a suit to the rotating shop. Error: " + ex);
225 | }
226 | }
227 | else if (valueData == "KEYWORD")
228 | {
229 | newMaterial.EnableKeyword(keyData);
230 | }
231 | else if (valueData == "DISABLEKEYWORD")
232 | {
233 | newMaterial.DisableKeyword(keyData);
234 | }
235 | else if (valueData == "SHADERPASS")
236 | {
237 | newMaterial.SetShaderPassEnabled(keyData, true);
238 | }
239 | else if (valueData == "DISABLESHADERPASS")
240 | {
241 | newMaterial.SetShaderPassEnabled(keyData, false);
242 | }
243 | else if (keyData == "SHADER")
244 | {
245 | Shader newShader = Shader.Find(valueData);
246 | newMaterial.shader = newShader;
247 | }
248 | else if (keyData == "MATERIAL")
249 | {
250 | foreach (Material material in customMaterials)
251 | {
252 | if (material.name == valueData)
253 | {
254 | newMaterial = Instantiate(material);
255 | newMaterial.mainTexture = texture;
256 | break;
257 | }
258 | }
259 | }
260 | else if (keyData == "AUDIOCLIP")
261 | {
262 | foreach (AudioClip audioClip in customAudioClips)
263 | {
264 | if (audioClip.name == valueData)
265 | {
266 | newSuit.jumpAudio = audioClip;
267 | break;
268 | }
269 | }
270 | }
271 | else if (float.TryParse(valueData, out float floatValue))
272 | {
273 | newMaterial.SetFloat(keyData, floatValue);
274 | }
275 | else if (TryParseVector4(valueData, out Vector4 vectorValue))
276 | {
277 | newMaterial.SetVector(keyData, vectorValue);
278 | }
279 | }
280 | }
281 | }
282 | }
283 | catch (Exception ex)
284 | {
285 | Debug.Log("Something went wrong with More Suits! Error: " + ex);
286 | }
287 |
288 | newSuit.suitMaterial = newMaterial;
289 |
290 | if (newSuit.unlockableName.ToLower() != "default")
291 | {
292 | if (addedSuitCount == MaxSuits)
293 | {
294 | Debug.Log("Attempted to add a suit, but you've already reached the max number of suits! Modify the config if you want more.");
295 | }
296 | else
297 | {
298 | __instance.unlockablesList.unlockables.Add(newSuit);
299 | addedSuitCount++;
300 | }
301 | }
302 | }
303 |
304 | SuitsAdded = true;
305 | break;
306 | }
307 | }
308 |
309 | UnlockableItem dummySuit = JsonUtility.FromJson(JsonUtility.ToJson(originalSuit));
310 | dummySuit.alreadyUnlocked = false;
311 | dummySuit.hasBeenMoved = false;
312 | dummySuit.placedPosition = Vector3.zero;
313 | dummySuit.placedRotation = Vector3.zero;
314 | dummySuit.unlockableType = 753; // this unlockable type is not used
315 | while (__instance.unlockablesList.unlockables.Count < originalUnlockablesCount + MaxSuits)
316 | {
317 | __instance.unlockablesList.unlockables.Add(dummySuit);
318 | }
319 | }
320 | }
321 | catch (Exception ex)
322 | {
323 | Debug.Log("Something went wrong with More Suits! Error: " + ex);
324 | }
325 |
326 | }
327 |
328 | [HarmonyPatch("PositionSuitsOnRack")]
329 | [HarmonyPrefix]
330 | static bool PositionSuitsOnRackPatch(ref StartOfRound __instance)
331 | {
332 | List suits = UnityEngine.Object.FindObjectsOfType().ToList();
333 | suits = suits.OrderBy(suit => suit.syncedSuitID.Value).ToList();
334 | int index = 0;
335 | foreach (UnlockableSuit suit in suits)
336 | {
337 | AutoParentToShip component = suit.gameObject.GetComponent();
338 | component.overrideOffset = true;
339 |
340 | float offsetModifier = 0.18f;
341 | if (MakeSuitsFitOnRack && suits.Count > 13)
342 | {
343 | offsetModifier = offsetModifier / (Math.Min(suits.Count, 20) / 12f); // squish the suits together to make them all fit
344 | }
345 |
346 | component.positionOffset = new Vector3(-2.45f, 2.75f, -8.41f) + __instance.rightmostSuitPosition.forward * offsetModifier * (float)index;
347 | component.rotationOffset = new Vector3(0f, 90f, 0f);
348 |
349 | index++;
350 | }
351 |
352 | return false; // don't run the original
353 | }
354 | }
355 |
356 | private static TerminalNode cancelPurchase;
357 | private static TerminalKeyword buyKeyword;
358 | private static UnlockableItem AddToRotatingShop(UnlockableItem newSuit, int price, int unlockableID)
359 | {
360 | Terminal terminal = UnityEngine.Object.FindObjectOfType();
361 | for (int i = 0; i < terminal.terminalNodes.allKeywords.Length; i++)
362 | {
363 | if (terminal.terminalNodes.allKeywords[i].name == "Buy")
364 | {
365 | buyKeyword = terminal.terminalNodes.allKeywords[i];
366 | break;
367 | }
368 | }
369 |
370 | newSuit.alreadyUnlocked = false;
371 | newSuit.hasBeenMoved = false;
372 | newSuit.placedPosition = Vector3.zero;
373 | newSuit.placedRotation = Vector3.zero;
374 |
375 | newSuit.shopSelectionNode = ScriptableObject.CreateInstance();
376 | newSuit.shopSelectionNode.name = newSuit.unlockableName + "SuitBuy1";
377 | newSuit.shopSelectionNode.creatureName = newSuit.unlockableName + " suit";
378 | newSuit.shopSelectionNode.displayText = "You have requested to order " + newSuit.unlockableName + " suits.\nTotal cost of item: [totalCost].\n\nPlease CONFIRM or DENY.\n\n";
379 | newSuit.shopSelectionNode.clearPreviousText = true;
380 | newSuit.shopSelectionNode.shipUnlockableID = unlockableID;
381 | newSuit.shopSelectionNode.itemCost = price;
382 | newSuit.shopSelectionNode.overrideOptions = true;
383 |
384 | CompatibleNoun confirm = new CompatibleNoun();
385 | confirm.noun = ScriptableObject.CreateInstance();
386 | confirm.noun.word = "confirm";
387 | confirm.noun.isVerb = true;
388 |
389 | confirm.result = ScriptableObject.CreateInstance();
390 | confirm.result.name = newSuit.unlockableName + "SuitBuyConfirm";
391 | confirm.result.creatureName = "";
392 | confirm.result.displayText = "Ordered " + newSuit.unlockableName + " suits! Your new balance is [playerCredits].\n\n";
393 | confirm.result.clearPreviousText = true;
394 | confirm.result.shipUnlockableID = unlockableID;
395 | confirm.result.buyUnlockable = true;
396 | confirm.result.itemCost = price;
397 | confirm.result.terminalEvent = "";
398 |
399 | CompatibleNoun deny = new CompatibleNoun();
400 | deny.noun = ScriptableObject.CreateInstance();
401 | deny.noun.word = "deny";
402 | deny.noun.isVerb = true;
403 |
404 | if (cancelPurchase == null)
405 | {
406 | cancelPurchase = ScriptableObject.CreateInstance(); // we can use the same Cancel Purchase node
407 | }
408 | deny.result = cancelPurchase;
409 | deny.result.name = "MoreSuitsCancelPurchase";
410 | deny.result.displayText = "Cancelled order.\n";
411 |
412 | newSuit.shopSelectionNode.terminalOptions = new CompatibleNoun[] { confirm, deny };
413 |
414 | TerminalKeyword suitKeyword = ScriptableObject.CreateInstance();
415 | suitKeyword.name = newSuit.unlockableName + "Suit";
416 | suitKeyword.word = newSuit.unlockableName.ToLower() + " suit";
417 | suitKeyword.defaultVerb = buyKeyword;
418 |
419 | CompatibleNoun suitCompatibleNoun = new CompatibleNoun();
420 | suitCompatibleNoun.noun = suitKeyword;
421 | suitCompatibleNoun.result = newSuit.shopSelectionNode;
422 | List buyKeywordList = buyKeyword.compatibleNouns.ToList();
423 | buyKeywordList.Add(suitCompatibleNoun);
424 | buyKeyword.compatibleNouns = buyKeywordList.ToArray();
425 |
426 | List allKeywordsList = terminal.terminalNodes.allKeywords.ToList();
427 | allKeywordsList.Add(suitKeyword);
428 | allKeywordsList.Add(confirm.noun);
429 | allKeywordsList.Add(deny.noun);
430 | terminal.terminalNodes.allKeywords = allKeywordsList.ToArray();
431 |
432 | return newSuit;
433 | }
434 |
435 | public static bool TryParseVector4(string input, out Vector4 vector)
436 | {
437 | vector = Vector4.zero;
438 |
439 | string[] components = input.Split(',');
440 |
441 | if (components.Length == 4)
442 | {
443 | if (float.TryParse(components[0], out float x) &&
444 | float.TryParse(components[1], out float y) &&
445 | float.TryParse(components[2], out float z) &&
446 | float.TryParse(components[3], out float w))
447 | {
448 | vector = new Vector4(x, y, z, w);
449 | return true;
450 | }
451 | }
452 |
453 | return false;
454 | }
455 | }
456 | }
--------------------------------------------------------------------------------