diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/DraftSchematicLoader.kt b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/DraftSchematicLoader.kt new file mode 100644 index 000000000..7b2d5eeaf --- /dev/null +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/DraftSchematicLoader.kt @@ -0,0 +1,214 @@ +/*********************************************************************************** + * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + ***********************************************************************************/ +package com.projectswg.holocore.resources.support.data.server_info.loader + +import com.projectswg.common.data.CRC +import com.projectswg.common.data.encodables.oob.StringId +import com.projectswg.common.data.schematic.DraftSchematic +import com.projectswg.common.data.schematic.DraftSlotDataOption +import com.projectswg.common.data.schematic.IngridientSlot +import com.projectswg.common.data.schematic.IngridientSlot.IngridientType +import com.projectswg.common.data.swgfile.ClientFactory +import com.projectswg.common.data.swgfile.visitors.ObjectData +import com.projectswg.holocore.resources.support.data.server_info.StandardLog +import me.joshlarson.json.JSON +import me.joshlarson.json.JSONObject +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.text.Charsets.UTF_8 + +class DraftSchematicLoader : DataLoader() { + + private val draftSchematics: MutableMap = HashMap() + + fun getDraftSchematic(draftSchematicIff: String): DraftSchematic? { + return draftSchematics[draftSchematicIff] + } + + override fun load() { + val what = "draft schematics" + val start = StandardLog.onStartLoad(what) + + loadAllDraftSchematics() + + StandardLog.onEndLoad(draftSchematics.size, what, start) + } + + private fun loadAllDraftSchematics() { + val files = findAllDraftSchematicJsonFiles() + + for (file in files) { + val jsonFilePath = file.path + val iffDraftSchematicPath = jsonFilePath.replace("\\", "/").replaceFirst("serverdata/", "object/").replace(".json", ".iff") + val fileToJsonString = fileToJsonString(file) + val sharedIffDraftSchematicPath = ClientFactory.formatToSharedFile(iffDraftSchematicPath) + val draftSchematic = jsonToDraftSchematic(fileToJsonString, sharedIffDraftSchematicPath) + + draftSchematics[sharedIffDraftSchematicPath] = draftSchematic + } + } + + private fun findAllDraftSchematicJsonFiles(): List { + val base = Paths.get("serverdata/draft_schematic") + val pathStream = Files.find(base, 10, { path, _ -> path.toString().endsWith(".json") }) + + return pathStream.map { it.toFile() }.toList() + } + + private fun fileToJsonString(file: File): String { + return file.readText(charset = UTF_8) + } + + private fun jsonToDraftSchematic(json: String, iffDraftSchematicPath: String): DraftSchematic { + val draftSchematic = DraftSchematic() + val jsonObject = JSON.readObject(json) + + setItemsPerContainer(jsonObject, draftSchematic) + setCraftedSharedTemplate(jsonObject, draftSchematic) + setCombinedCrc(iffDraftSchematicPath, draftSchematic) + setVolume(jsonObject, draftSchematic) + setComplexity(jsonObject, draftSchematic) + setSlots(jsonObject, draftSchematic) + + return draftSchematic + } + + private fun setSlots(jsonObject: JSONObject, draftSchematic: DraftSchematic) { + if (jsonObject.containsKey("slots")) { + val array = jsonObject.getArray("slots") + for (any in array) { + val slotObject = any as Map<*, *> + val name = stringIdName(slotObject) + val optional = slotObject["optional"] as Boolean + val slot = IngridientSlot(name, optional) + draftSchematic.ingridientSlot.add(slot) + + setOptions(slotObject, slot) + } + } + } + + private fun setOptions(slotObject: Map<*, *>, slot: IngridientSlot) { + val options = slotObject["options"] as List> + for (option in options) { + val ingredientType = ingridientType(option) + setIngredients(option, slot, ingredientType) + } + } + + private fun ingridientType(option: Map<*, *>) : IngridientType { + val value = option["ingredientType"] as String + + return when (value) { + "IT_none" -> IngridientType.IT_NONE + "IT_resourceType" -> IngridientType.IT_RESOURCE_TYPE + "IT_resourceClass" -> IngridientType.IT_RESOURCE_CLASS + "IT_template" -> IngridientType.IT_TEMPLATE + "IT_templateGeneric" -> IngridientType.IT_TEMPLATE + "IT_schematic" -> IngridientType.IT_SCHEMATIC + else -> throw IllegalArgumentException("Unknown ingredient type: $value. Maybe it just needs to be mapped?") + } + } + + private fun setIngredients(option: Map<*, *>, slot: IngridientSlot, ingredientType: IngridientType) { + val ingredients = option["ingredients"] as List> + + for (ingredient in ingredients) { + val name = stringIdName(ingredient) + val ingredientName = ingredient["ingredient"] as String + val amount = (ingredient["count"] as Long).toInt() + slot.addSlotDataOption(DraftSlotDataOption(name, resolveIngredientName(ingredientName), ingredientType.slotType, amount)) + } + } + + private fun setComplexity(jsonObject: JSONObject, draftSchematic: DraftSchematic) { + val complexity = jsonObject["complexity"] as Long? + if (complexity != null) { + draftSchematic.complexity = complexity.toInt() + } + } + + private fun setVolume(jsonObject: JSONObject, draftSchematic: DraftSchematic) { + val volume = jsonObject["volume"] as Long? + if (volume != null) { + draftSchematic.volume = volume.toInt() + } + } + + private fun setItemsPerContainer(jsonObject: JSONObject, draftSchematic: DraftSchematic) { + val itemsPerContainer = jsonObject["itemsPerContainer"] as Long? + if (itemsPerContainer != null) { + draftSchematic.itemsPerContainer = itemsPerContainer.toInt() + draftSchematic.isCanManufacture = itemsPerContainer > 0 + } + } + + private fun setCraftedSharedTemplate(jsonObject: JSONObject, draftSchematic: DraftSchematic) { + val craftedObjectTemplate = jsonObject["craftedObjectTemplate"] as String? + if (!craftedObjectTemplate.isNullOrEmpty()) { + draftSchematic.craftedSharedTemplate = ClientFactory.formatToSharedFile(craftedObjectTemplate) + } + } + + private fun setCombinedCrc(iffDraftSchematicPath: String, draftSchematic: DraftSchematic) { + val serverCrc = getDraftSchematicServerCrc(iffDraftSchematicPath) + val clientCrc = getDraftSchematicClientCrc(iffDraftSchematicPath) + val combinedCrc = combinedCrc(serverCrc = serverCrc, clientCrc = clientCrc) + draftSchematic.combinedCrc = combinedCrc + } + + private fun stringIdName(map: Map<*, *>): StringId { + val nameStrings = map["name"] as List + return StringId(nameStrings[0], nameStrings[1]) + } + + private fun getDraftSchematicServerCrc(schematicInGroupShared: String): Int { + return CRC.getCrc(schematicInGroupShared) + } + + private fun getDraftSchematicClientCrc(schematicInGroupShared: String): Int { + val templateWithoutPrefix = schematicInGroupShared.replace("object/draft_schematic/", "") + return CRC.getCrc(templateWithoutPrefix) + } + + private fun combinedCrc(serverCrc: Int, clientCrc: Int): Long { + return serverCrc.toLong() shl 32 and -0x100000000L or (clientCrc.toLong() and 0x00000000FFFFFFFFL) + } + + private fun resolveIngredientName(ingredientName: String): String { + if (ingredientName.endsWith(".iff")) { + val attributes = ServerData.objectData.getAttributes(ClientFactory.formatToSharedFile(ingredientName)) + if (attributes != null) { + val stringId = attributes[ObjectData.ObjectDataAttribute.OBJECT_NAME] as StringId + return stringId.toString() + } + } + + return ingredientName + } +} \ No newline at end of file diff --git a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/ServerData.kt b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/ServerData.kt index 657e315ca..65163ce1c 100644 --- a/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/ServerData.kt +++ b/src/main/java/com/projectswg/holocore/resources/support/data/server_info/loader/ServerData.kt @@ -113,6 +113,7 @@ object ServerData { val staticCityPoints by SoftDataLoaderDelegate(::StaticCityPointLoader) val npcEquipment by SoftDataLoaderDelegate(::NpcEquipmentLoader) val schematicGroups by SoftDataLoaderDelegate(::SchematicGroupLoader) + val draftSchematics by SoftDataLoaderDelegate(::DraftSchematicLoader) private class WeakDataLoaderDelegate(loaderCreator: () -> T): DataLoaderDelegate(::WeakReference, loaderCreator) private class SoftDataLoaderDelegate(loaderCreator: () -> T): DataLoaderDelegate(::SoftReference, loaderCreator) diff --git a/src/test/java/com/projectswg/holocore/resources/support/data/server_info/loader/DraftSchematicLoaderTest.kt b/src/test/java/com/projectswg/holocore/resources/support/data/server_info/loader/DraftSchematicLoaderTest.kt new file mode 100644 index 000000000..f4d4e246f --- /dev/null +++ b/src/test/java/com/projectswg/holocore/resources/support/data/server_info/loader/DraftSchematicLoaderTest.kt @@ -0,0 +1,142 @@ +/*********************************************************************************** + * Copyright (c) 2023 /// Project SWG /// www.projectswg.com * + * * + * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * + * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * + * Our goal is to create an emulator which will provide a server for players to * + * continue playing a game similar to the one they used to play. We are basing * + * it on the final publish of the game prior to end-game events. * + * * + * This file is part of Holocore. * + * * + * --------------------------------------------------------------------------------* + * * + * Holocore is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 of the * + * License, or (at your option) any later version. * + * * + * Holocore is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with Holocore. If not, see . * + ***********************************************************************************/ +package com.projectswg.holocore.resources.support.data.server_info.loader + +import com.projectswg.common.data.encodables.oob.StringId +import com.projectswg.common.data.schematic.SlotType +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class DraftSchematicLoaderTest { + + @Test + fun craftedTemplateIsTransformedIntoSharedTemplate() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals("object/tangible/deed/camp_deed/shared_camp_luxury_deed.iff", draftSchematic.craftedSharedTemplate) + } + + @Test + fun itemsPerContainer() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(10, draftSchematic.itemsPerContainer) + } + + @Test + fun volume() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(1, draftSchematic.volume) + } + + @Test + fun complexity() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(17, draftSchematic.complexity) + } + + @Test + fun combinedCrc() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/instrument/shared_instrument_slitherhorn.iff") ?: fail("Could not find draft schematic") + + assertEquals(8706505225174593761, draftSchematic.combinedCrc) + } + + @Nested + inner class Slots { + + @Test + fun allSlotsAreLoaded() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(10, draftSchematic.ingridientSlot.size) + } + + @Test + fun optional() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertFalse(draftSchematic.ingridientSlot[0].isOptional) + } + + @Test + fun name() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(StringId("craft_item_ingredients_n", "shelter_panels"), draftSchematic.ingridientSlot[0].name) + } + + @Nested + inner class Ingredients { + + @Test + fun ingredientType() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(SlotType.RESOURCES, draftSchematic.ingridientSlot[0].fromSlotDataOption[0].slotType) + } + + @Test + fun allIngredientsAreLoaded() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(1, draftSchematic.ingridientSlot[0].fromSlotDataOption.size) + } + + @Test + fun name() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(StringId("craft_item_ingredients_n", "shelter_panels"), draftSchematic.ingridientSlot[0].fromSlotDataOption[0].stfName) + } + + @Test + fun ingredient() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals("aluminum", draftSchematic.ingridientSlot[0].fromSlotDataOption[0].ingredientName) + } + + @Test + fun ingredientIffResolved() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/weapon/shared_bowcaster_assault.iff") ?: fail("Could not find draft schematic") + + assertEquals("@craft_weapon_ingredients_n:blaster_power_handler_advanced", draftSchematic.ingridientSlot[4].fromSlotDataOption[0].ingredientName) + } + + @Test + fun count() { + val draftSchematic = ServerData.draftSchematics.getDraftSchematic("object/draft_schematic/camp/shared_camp_luxury.iff") ?: fail("Could not find draft schematic") + + assertEquals(250, draftSchematic.ingridientSlot[0].fromSlotDataOption[0].amount) + } + } + } +} \ No newline at end of file