diff --git a/src/main/java/com/projectswg/holocore/resources/support/objects/StaticItemCreator.kt b/src/main/java/com/projectswg/holocore/resources/support/objects/StaticItemCreator.kt
new file mode 100644
index 000000000..bec6c1e61
--- /dev/null
+++ b/src/main/java/com/projectswg/holocore/resources/support/objects/StaticItemCreator.kt
@@ -0,0 +1,251 @@
+/***********************************************************************************
+ * Copyright (c) 2019 /// 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.objects
+
+import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader
+import com.projectswg.holocore.resources.support.data.server_info.loader.StaticItemLoader
+import com.projectswg.holocore.resources.support.objects.swg.SWGObject
+import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject
+import com.projectswg.holocore.resources.support.objects.swg.weapon.WeaponObject
+import java.util.*
+import kotlin.math.floor
+
+object StaticItemCreator {
+
+ fun createItem(itemName: String): SWGObject? {
+ val info = DataLoader.staticItems().getItemByName(itemName) ?: return null
+ val swgObject = ObjectCreator.createObjectFromTemplate(info.iffTemplate) as? TangibleObject ?: return null
+
+ applyAttributes(swgObject, info)
+ return swgObject
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.StaticItemInfo) {
+ obj.addAttribute("condition", String.format("%d/%d", info.hitPoints, info.hitPoints))
+ obj.addAttribute("volume", info.volume.toString())
+ obj.objectName = info.stringName
+
+ applyAttributes(obj, info.armorInfo)
+ applyAttributes(obj, info.wearableInfo)
+ applyAttributes(obj, info.weaponInfo)
+ applyAttributes(obj, info.collectionInfo)
+ applyAttributes(obj, info.costumeInfo)
+ applyAttributes(obj, info.dnaInfo)
+ applyAttributes(obj, info.grantInfo)
+ applyAttributes(obj, info.genericInfo)
+ applyAttributes(obj, info.objectInfo)
+ applyAttributes(obj, info.schematicInfo)
+ applyAttributes(obj, info.storytellerInfo)
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.ArmorItemInfo?) {
+ if (info == null)
+ return
+
+ if (info.requiredProfession.isNotEmpty())
+ obj.addAttribute("class_required", "@ui_roadmap:title_" + info.requiredProfession)
+ obj.addAttribute("required_combat_level", info.requiredLevel.toString())
+ val kineticMax: Int
+ val energyMax: Int
+ when (info.armorType) {
+ StaticItemLoader.ArmorItemInfo.ArmorType.ASSAULT -> {
+ kineticMax = 7000
+ energyMax = 5000
+ obj.addAttribute("armor_category", "@obj_attr_n:armor_assault")
+ }
+ StaticItemLoader.ArmorItemInfo.ArmorType.BATTLE -> {
+ kineticMax = 6000
+ energyMax = 6000
+ obj.addAttribute("armor_category", "@obj_attr_n:armor_battle")
+ }
+ StaticItemLoader.ArmorItemInfo.ArmorType.RECON -> {
+ kineticMax = 5000
+ energyMax = 7000
+ obj.addAttribute("armor_category", "@obj_attr_n:armor_reconnaissance")
+ }
+ }
+
+ obj.addAttribute("cat_armor_standard_protection.kinetic", calculateProtection(kineticMax, info.protection))
+ obj.addAttribute("cat_armor_standard_protection.energy", calculateProtection(energyMax, info.protection))
+ obj.addAttribute("cat_armor_special_protection.special_protection_type_heat", calculateProtection(6000, info.protection))
+ obj.addAttribute("cat_armor_special_protection.special_protection_type_cold", calculateProtection(6000, info.protection))
+ obj.addAttribute("cat_armor_special_protection.special_protection_type_acid", calculateProtection(6000, info.protection))
+ obj.addAttribute("cat_armor_special_protection.special_protection_type_electricity", calculateProtection(6000, info.protection))
+
+ applySkillMods(obj, info.skillMods)
+ applyColors(obj, info.color)
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.WearableItemInfo?) {
+ if (info == null)
+ return
+
+ if (info.requiredProfession.isNotEmpty())
+ obj.addAttribute("class_required", "@ui_roadmap:title_" + info.requiredProfession)
+ obj.addAttribute("required_combat_level", info.requiredLevel.toString())
+
+ if (info.requiredFaction.isNotEmpty())
+ obj.addAttribute("faction_restriction", "@pvp_factions:" + info.requiredFaction)
+
+ // Apply the mods!
+ applySkillMods(obj, info.skillMods)
+ applyColors(obj, info.color)
+
+ // Add the race restrictions only if there are any
+ val raceRestriction = buildRaceRestrictionString(info)
+ if (raceRestriction.isNotEmpty())
+ obj.addAttribute("species_restrictions.species_name", raceRestriction)
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.WeaponItemInfo?) {
+ if (info == null)
+ return
+
+ if (info.requiredProfession.isNotEmpty())
+ obj.addAttribute("class_required", "@ui_roadmap:title_" + info.requiredProfession)
+ obj.addAttribute("required_combat_level", info.requiredLevel.toString())
+ obj.addAttribute("cat_wpn_damage.wpn_damage_type", "@obj_attr_n:${info.damageType.name.toLowerCase(Locale.US)}")
+ obj.addAttribute("cat_wpn_damage.wpn_category", "@obj_attr_n:wpn_category_" + info.weaponType.num)
+ obj.addAttribute("cat_wpn_damage.wpn_attack_speed", (info.attackSpeed / 100).toString())
+ obj.addAttribute("cat_wpn_damage.damage", "${info.minDamage}-${info.maxDamage}")
+ if (info.elementalType != null) { // Not all weapons have elemental damage.
+ obj.addAttribute("cat_wpn_damage.wpn_elemental_type", "@obj_attr_n:${info.elementalType.name.toLowerCase(Locale.US)}")
+ obj.addAttribute("cat_wpn_damage.wpn_elemental_value", info.elementalDamage.toString())
+ }
+
+ obj.addAttribute("cat_wpn_damage.weapon_dps", info.actualDps.toString())
+
+ if (info.procEffect.isNotEmpty())
+ // Not all weapons have a proc effect
+ obj.addAttribute("proc_name", info.procEffect)
+
+ // TODO set DPS
+
+ obj.addAttribute("cat_wpn_other.wpn_range", String.format("%d-%dm", info.minRange, info.maxRange))
+ // Ziggy: Special Action Cost would go under cat_wpn_other as well, but it's a pre-NGE artifact.
+
+ val weapon = obj as WeaponObject
+ weapon.type = info.weaponType
+ weapon.attackSpeed = info.attackSpeed.toFloat()
+ weapon.minRange = info.minRange.toFloat()
+ weapon.maxRange = info.maxRange.toFloat()
+ weapon.damageType = info.damageType
+ weapon.elementalType = info.elementalType
+ weapon.minDamage = info.minDamage
+ weapon.maxDamage = info.maxDamage
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.CollectionItemInfo?) {
+ if (info == null)
+ return
+
+ obj.addAttribute("collection_name", info.slotName)
+
+ applyColors(obj, info.color)
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.CostumeItemInfo?) {
+// if (info == null)
+// return;
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.DnaItemInfo?) {
+// if (info == null)
+// return;
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.GrantItemInfo?) {
+// if (info == null)
+// return;
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.GenericItemInfo?) {
+ if (info == null)
+ return
+
+ if (info.value != 0)
+ obj.addAttribute("charges", info.value.toString())
+
+ applyColors(obj, info.color)
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.ObjectItemInfo?) {
+ if (info == null)
+ return
+
+ if (info.value != 0)
+ obj.addAttribute("charges", info.value.toString())
+
+ applyColors(obj, info.color)
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.SchematicItemInfo?) {
+// if (info == null)
+// return;
+ }
+
+ private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.StorytellerItemInfo?) {
+// if (info == null)
+// return;
+ }
+
+ private fun applySkillMods(obj: TangibleObject, skillMods: Map) {
+ for ((key, value) in skillMods)
+ obj.addAttribute(key, value.toString())
+ }
+
+ private fun applyColors(obj: TangibleObject, colors: IntArray) {
+ for (i in 0..3) {
+ if (colors[i] >= 0) {
+ obj.putCustomization("/private/index_color_$i", colors[i])
+ }
+ }
+ }
+
+ private fun buildRaceRestrictionString(info: StaticItemLoader.WearableItemInfo): String {
+ var races = ""
+
+ if (info.isRaceWookie)
+ races += "Wookiee "
+ if (info.isRaceIthorian)
+ races += "Ithorian "
+ if (info.isRaceRodian)
+ races += "Rodian "
+ if (info.isRaceTrandoshan)
+ races += "Trandoshan "
+ if (info.isRaceRest)
+ races += "MonCal Human Zabrak Bothan Sullustan Twi'lek "
+
+ return if (races.isEmpty()) "" else races.substring(0, races.length - 1)
+ }
+
+ private fun calculateProtection(max: Int, protection: Double): String {
+ return floor(max * protection).toInt().toString()
+ }
+
+}
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java
index 0880f25a3..fae1faad5 100644
--- a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/GrantLootService.java
@@ -256,6 +256,8 @@ public final class GrantLootService extends Service {
switch (item.moveToContainer(looter, looter.getInventory())) {
case SUCCESS: {
String itemName = item.getObjectName();
+ if (itemName.isEmpty())
+ itemName = item.getStringId().toString();
if (item instanceof CreditObject) {
onLootedCredits(looter, ((CreditObject) item).getAmount());
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.kt b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.kt
index 47538461c..0334f0c7a 100644
--- a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.kt
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/LootGenerationService.kt
@@ -1,35 +1,40 @@
package com.projectswg.holocore.services.gameplay.combat.loot
import com.projectswg.common.data.location.Location
+import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage
import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectTransformMessage
+import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage
+import com.projectswg.common.network.packets.swg.zone.object_controller.ShowLootBox
import com.projectswg.holocore.intents.gameplay.combat.CreatureKilledIntent
import com.projectswg.holocore.intents.gameplay.combat.loot.CorpseLootedIntent
import com.projectswg.holocore.intents.gameplay.combat.loot.LootGeneratedIntent
-import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent
-import com.projectswg.holocore.intents.support.objects.items.CreateStaticItemIntent
import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent
import com.projectswg.holocore.resources.support.data.server_info.StandardLog
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData
import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase
import com.projectswg.holocore.resources.support.objects.ObjectCreator
import com.projectswg.holocore.resources.support.objects.swg.SWGObject
-import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject
import com.projectswg.holocore.resources.support.objects.swg.group.GroupObject
-import com.projectswg.holocore.resources.support.objects.swg.tangible.CreditObject
+import com.projectswg.holocore.services.gameplay.combat.loot.generation.CreditLootGenerator
+import com.projectswg.holocore.services.gameplay.combat.loot.generation.ItemLootGenerator
+import com.projectswg.holocore.services.gameplay.combat.loot.generation.NPCLootTable
+import com.projectswg.holocore.services.gameplay.combat.loot.generation.RareItemLootGenerator
import com.projectswg.holocore.services.support.objects.ObjectStorageService.ObjectLookup
-import com.projectswg.holocore.services.support.objects.items.StaticItemService
-import me.joshlarson.jlcommon.control.IntentChain
+import kotlinx.coroutines.*
import me.joshlarson.jlcommon.control.IntentHandler
import me.joshlarson.jlcommon.control.Service
import me.joshlarson.jlcommon.log.Log
import java.util.*
-import java.util.concurrent.ThreadLocalRandom
class LootGenerationService : Service() {
private val npcLoot: MutableMap = HashMap() // K: npc_id, V: possible loot
+ private val creditGenerator = CreditLootGenerator()
+ private val itemGenerator = ItemLootGenerator()
+ private val rareItemLootGenerator = RareItemLootGenerator()
+ private val scope = CoroutineScope(Dispatchers.Default)
override fun initialize(): Boolean {
loadNPCLoot()
@@ -37,36 +42,58 @@ class LootGenerationService : Service() {
return true
}
+ override fun stop(): Boolean {
+ scope.coroutineContext.cancel()
+ return true
+ }
+
@IntentHandler
private fun handleCreatureKilled(cki: CreatureKilledIntent) {
val corpse = cki.corpse as? AIObject ?: return
val creatureId = corpse.creatureId
- val loot = npcLoot[creatureId]
+ val lootRecord = npcLoot[creatureId]
- if (loot == null) {
+ if (lootRecord == null) {
Log.w("No NPCLoot associated with NPC ID: $creatureId")
return
}
+ val loot = ArrayList()
+ val killer = cki.killer
+ if (!killer.isPlayer)
+ return
+
+ // Various kinds of loot
+ if (lootRecord.isDropCredits && PswgDatabase.config.getBoolean(this, "cashLoot", true))
+ creditGenerator.generate(corpse, loot)
+ if (PswgDatabase.config.getBoolean(this, "itemLoot", true))
+ itemGenerator.generate(corpse, killer, loot, lootRecord.npcTables)
+ rareItemLootGenerator.generate(corpse = corpse, killer = killer, loot = loot)
+
+ if (loot.isEmpty()) {
+ CorpseLootedIntent(corpse).broadcast()
+ return
+ }
+
val lootInventory = ObjectCreator.createObjectFromTemplate("object/tangible/inventory/shared_creature_inventory.iff")
+ loot.forEach { obj ->
+ obj.moveToContainer(lootInventory)
+ ObjectCreatedIntent.broadcast(obj)
+
+ scope.launch {
+ delay(60)
+ when (obj.template) {
+ RareLootService.RARE_CHEST,
+ RareLootService.LEGENDARY_CHEST,
+ RareLootService.EXCEPTIONAL_CHEST -> sendRareLootPackets(obj, corpse, killer)
+ }
+ }
+ }
lootInventory.moveToContainer(corpse, corpse.location)
ObjectCreatedIntent.broadcast(lootInventory)
- val killer = cki.killer
-
- var cashGenerated = false
- var lootGenerated = false
-
- if (PswgDatabase.config.getBoolean(this, "cashLoot", true))
- cashGenerated = generateCreditChip(loot, lootInventory, corpse.difficulty, corpse.level.toInt())
- if (PswgDatabase.config.getBoolean(this, "itemLoot", true))
- lootGenerated = generateLoot(loot, killer, lootInventory, corpse.difficulty, corpse.level.toInt())
-
- if (!cashGenerated && !lootGenerated)
- CorpseLootedIntent(corpse).broadcast()
- else
- showLootDisc(killer, corpse)
+ showLootDisc(killer, corpse)
}
/**
@@ -100,7 +127,7 @@ class LootGenerationService : Service() {
if (chance <= 0)
return
- loot.npcTables.add(NPCTable(chance, table))
+ loot.npcTables.add(NPCLootTable(chance, table))
}
private fun showLootDisc(requester: CreatureObject, corpse: AIObject) {
@@ -122,125 +149,14 @@ class LootGenerationService : Service() {
LootGeneratedIntent.broadcast(corpse)
}
- private fun generateCreditChip(loot: NPCLoot, lootInventory: SWGObject, difficulty: CreatureDifficulty, combatLevel: Int): Boolean {
- if (!loot.isDropCredits)
- return false
-
- val random = ThreadLocalRandom.current()
- val config = PswgDatabase.config
-
- val range = config.getDouble(this, "lootCashHumanRange", 0.05)
- val cashLootRoll = random.nextDouble()
-
- val credits = combatLevel * (1 + random.nextDouble(0.0, range)).toInt() * when (difficulty) {
- CreatureDifficulty.NORMAL -> {
- if (cashLootRoll > config.getDouble(this, "lootCashHumanNormalChance", 0.60))
- return false
- config.getInt(this, "lootCashHumanNormal", 2)
- }
- CreatureDifficulty.ELITE -> {
- if (cashLootRoll > config.getDouble(this, "lootCashHumanElitechance", 0.80))
- return false
- config.getInt(this, "lootCashHumanElite", 5)
- }
- CreatureDifficulty.BOSS -> // bosses always drop cash loot, so no need to check
- config.getInt(this, "lootCashHumanBoss", 9)
- }
-
- // TODO scale with group size?
-
- val cashObject = ObjectCreator.createObjectFromTemplate("object/tangible/item/shared_loot_cash.iff", CreditObject::class.java)
-
- cashObject.amount = credits.toLong()
- cashObject.moveToContainer(lootInventory)
-
- ObjectCreatedIntent.broadcast(cashObject)
- return true
+ private fun sendRareLootPackets(chest: SWGObject, corpse: CreatureObject, killer: CreatureObject) {
+ val effect = PlayClientEffectObjectMessage("appearance/pt_rare_chest.prt", "", corpse.objectId, "")
+ val sound = PlayMusicMessage(0, "sound/rare_loot_chest.snd", 1, false)
+ val box = ShowLootBox(killer.objectId, longArrayOf(chest.objectId))
+
+ killer.owner?.sendPacket(effect, sound, box)
}
- /**
- * Generates loot and places it in the inventory of the corpse.
- *
- * @param loot the loot info of the creature killed
- * @param killer the person that killed the NPC
- * @param lootInventory the inventory the loot will be placed in (corpse inventory)
- * @return whether loot was generated or not
- */
- private fun generateLoot(loot: NPCLoot, killer: CreatureObject, lootInventory: SWGObject, difficulty: CreatureDifficulty, level: Int): Boolean {
- val random = ThreadLocalRandom.current()
- var lootGenerated = false
-
- val tableRoll = random.nextInt(100) + 1
-
- // Admin Variables
- val admin = killer.hasCommand("admin")
- val adminOutput1 = StringBuilder("$tableRoll //")
- val adminOutput2 = StringBuilder()
-
- val lootTables = ServerData.lootTables
- for ((tableId, npcTable) in loot.npcTables.withIndex()) {
- val tableChance = npcTable.chance
-
- if (tableRoll > tableChance) {
- // Skip ahead if there's no drop chance
- adminOutput1.append("/ \\#FF0000 loot_table").append(tableId+1)
- break
- }
- adminOutput1.append("/ \\#00FF00 loot_table").append(tableId+1)
-
- val groupRoll = random.nextInt(100) + 1
- val lootTableItem = lootTables.getLootTableItem(npcTable.lootTable, difficulty.name, level) ?: continue
-
- for ((chance, items) in lootTableItem.groups) { // group of items to be granted
- if (groupRoll > chance)
- continue
-
- val itemName = items[random.nextInt(items.size)]
-
- adminOutput2.append(itemName).append('\n')
- when {
- itemName.startsWith("dynamic_") -> { // TODO dynamic item handling
- SystemMessageIntent(killer.owner!!, "We don't support this loot item yet: $itemName").broadcast()
- }
- itemName.endsWith(".iff") -> {
- val obj = ObjectCreator.createObjectFromTemplate(itemName)
- obj.moveToContainer(lootInventory)
- ObjectCreatedIntent.broadcast(obj)
-
- lootGenerated = true
- }
- else -> {
- CreateStaticItemIntent(killer, lootInventory, CreateStaticItemCallback(), itemName).broadcast()
-
- lootGenerated = true
- }
- }
-
- break
- }
- }
- if (admin) {
- IntentChain.broadcastChain(
- SystemMessageIntent(killer.owner!!, adminOutput1.toString()),
- SystemMessageIntent(killer.owner!!, adminOutput2.toString())
- )
- }
-
- return lootGenerated
- }
-
- private class NPCLoot(val isDropCredits: Boolean, val npcTables: MutableCollection = ArrayList())
-
- private class NPCTable(val chance: Int, val lootTable: String)
-
- private class CreateStaticItemCallback : StaticItemService.ObjectCreationHandler {
-
- override val isIgnoreVolume = true
-
- override fun success(createdObjects: List) {
- // do nothing - loot disc is created on the return of the generateLoot method
- }
-
- }
+ private class NPCLoot(val isDropCredits: Boolean, val npcTables: MutableList = ArrayList())
}
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/RareLootService.java b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/RareLootService.java
index 50d4ba64c..bb89c40ad 100644
--- a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/RareLootService.java
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/RareLootService.java
@@ -26,22 +26,14 @@
***********************************************************************************/
package com.projectswg.holocore.services.gameplay.combat.loot;
-import com.projectswg.common.data.encodables.oob.StringId;
-import com.projectswg.common.network.packets.swg.zone.PlayClientEffectObjectMessage;
-import com.projectswg.common.network.packets.swg.zone.PlayMusicMessage;
-import com.projectswg.common.network.packets.swg.zone.object_controller.ShowLootBox;
-import com.projectswg.holocore.intents.gameplay.combat.CreatureKilledIntent;
import com.projectswg.holocore.intents.gameplay.combat.loot.OpenRareChestIntent;
import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent;
import com.projectswg.holocore.intents.support.objects.items.CreateStaticItemIntent;
import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent;
-import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent;
import com.projectswg.holocore.resources.support.data.server_info.SdbLoader;
import com.projectswg.holocore.resources.support.data.server_info.StandardLog;
import com.projectswg.holocore.resources.support.global.player.Player;
-import com.projectswg.holocore.resources.support.objects.ObjectCreator;
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
-import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty;
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
import com.projectswg.holocore.services.support.objects.items.StaticItemService;
@@ -54,7 +46,6 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class RareLootService extends Service {
@@ -63,18 +54,11 @@ public class RareLootService extends Service {
public static final String EXCEPTIONAL_CHEST = "object/tangible/item/shared_rare_loot_chest_2.iff";
public static final String LEGENDARY_CHEST = "object/tangible/item/shared_rare_loot_chest_3.iff";
- // TODO these two could be config options
- private static final short MAX_LEVEL_DIFFERENCE = 6; // +-6 difference is allowed between killer and corpse
- private static final int DROP_CHANCE = 1; // One in a hundred eligible kills will drop a chest
-
- private final Random random;
private final List rareItems;
private final List exceptionalItems;
private final List legendaryItems;
public RareLootService() {
- random = new Random();
-
rareItems = new ArrayList<>();
exceptionalItems = new ArrayList<>();
legendaryItems = new ArrayList<>();
@@ -112,38 +96,6 @@ public class RareLootService extends Service {
return super.initialize();
}
- @IntentHandler
- private void handleCreatureKilledIntent(CreatureKilledIntent cki) {
- // TODO only the player that delivered the killing blow is considered for this
- CreatureObject corpse = cki.getCorpse();
- CreatureObject killer = cki.getKiller();
-
- if (!isPlayerEligible(killer.isPlayer(), corpse.isPlayer())) {
- return;
- }
-
- if (!isLevelEligible(corpse.getLevel(), killer.getLevel())) {
- return;
- }
-
- int roll = random.nextInt(100) + 1; // Rolls from 0 to 99, then we add 1 and it becomes 1 to 100
-
- if (!isDrop(roll)) {
- Log.d("No RLS drop from %s with roll %d", corpse, roll);
- return;
- }
-
- String template = templateForDifficulty(corpse.getDifficulty());
- SWGObject chest = ObjectCreator.createObjectFromTemplate(template);
- SWGObject inventory = killer.getSlottedObject("inventory");
-
- chest.setStf("loot_n", chestIdForTemplate(template) + "_n");
- chest.setDetailStf(new StringId("loot_n", chestIdForTemplate(template) + "_d")); // Not located in loot_d, for whatever reason...
-
- chest.moveToContainer(inventory);
- sendSuccessPackets(chest, corpse, killer);
- }
-
@IntentHandler
private void handleOpenRareChestIntent(OpenRareChestIntent intent) {
CreatureObject actor = intent.getActor();
@@ -183,50 +135,6 @@ public class RareLootService extends Service {
}
- boolean isPlayerEligible(boolean killerPlayer, boolean corpsePlayer) {
- return killerPlayer && !corpsePlayer;
- }
-
- boolean isLevelEligible(int corpseLevel, int killerLevel) {
- // Ensure a positive levelDifference
- int highestLevel = Math.max(corpseLevel, killerLevel);
- int lowestLevel = Math.min(corpseLevel, killerLevel);
- int levelDifference = highestLevel - lowestLevel;
-
- return levelDifference <= MAX_LEVEL_DIFFERENCE;
- }
-
- boolean isDrop(int roll) {
- return roll <= DROP_CHANCE;
- }
-
- String templateForDifficulty(CreatureDifficulty difficulty) {
- switch (difficulty) {
- case NORMAL:
- return RARE_CHEST;
- case ELITE:
- return EXCEPTIONAL_CHEST;
- case BOSS:
- return LEGENDARY_CHEST;
- default:
- throw new IllegalArgumentException("Unknown CreatureDifficulty: " + difficulty);
- }
- }
-
- String chestIdForTemplate(String template) {
- return template.replace("object/tangible/item/shared_", "").replace(".iff", "");
- }
-
- private void sendSuccessPackets(SWGObject chest, CreatureObject corpse, CreatureObject killer) {
- ObjectCreatedIntent.broadcast(chest);
-
- PlayClientEffectObjectMessage effect = new PlayClientEffectObjectMessage("appearance/pt_rare_chest.prt", "", corpse.getObjectId(), "");
- PlayMusicMessage sound = new PlayMusicMessage(0, "sound/rare_loot_chest.snd", 1, false);
- ShowLootBox box = new ShowLootBox(killer.getObjectId(), new long[] { chest.getObjectId() });
-
- killer.getOwner().sendPacket(effect, sound, box);
- }
-
private String randomItem(List items) {
int availableItems = items.size();
int randomIndex = ThreadLocalRandom.current().nextInt(availableItems);
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/CreditLootGenerator.kt b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/CreditLootGenerator.kt
new file mode 100644
index 000000000..39c6c3078
--- /dev/null
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/CreditLootGenerator.kt
@@ -0,0 +1,70 @@
+/***********************************************************************************
+ * Copyright (c) 2019 /// 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.services.gameplay.combat.loot.generation
+
+import com.projectswg.holocore.resources.support.data.server_info.mongodb.PswgDatabase
+import com.projectswg.holocore.resources.support.objects.ObjectCreator
+import com.projectswg.holocore.resources.support.objects.swg.SWGObject
+import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty
+import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
+import com.projectswg.holocore.resources.support.objects.swg.tangible.CreditObject
+import java.util.concurrent.ThreadLocalRandom
+
+class CreditLootGenerator() {
+
+ fun generate(corpse: CreatureObject, loot: MutableList) {
+ val random = ThreadLocalRandom.current()
+ val config = PswgDatabase.config
+
+ val range = config.getDouble(this, "lootCashHumanRange", 0.05)
+ val cashLootRoll = random.nextDouble()
+
+ val credits = corpse.level.toInt() * (1 + random.nextDouble(0.0, range)).toInt() * when (corpse.difficulty) {
+ CreatureDifficulty.ELITE -> {
+ if (cashLootRoll > config.getDouble(this, "lootCashHumanElitechance", 0.80))
+ return
+ config.getInt(this, "lootCashHumanElite", 5)
+ }
+ CreatureDifficulty.BOSS -> // bosses always drop cash loot, so no need to check
+ config.getInt(this, "lootCashHumanBoss", 9)
+ else -> {
+ if (cashLootRoll > config.getDouble(this, "lootCashHumanNormalChance", 0.60))
+ return
+ config.getInt(this, "lootCashHumanNormal", 2)
+ }
+ }
+
+ // TODO scale with group size?
+
+ val cashObject = ObjectCreator.createObjectFromTemplate("object/tangible/item/shared_loot_cash.iff", CreditObject::class.java)
+
+ cashObject.amount = credits.toLong()
+ loot.add(cashObject)
+ }
+
+}
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/ItemLootGenerator.kt b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/ItemLootGenerator.kt
new file mode 100644
index 000000000..8a8501f00
--- /dev/null
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/ItemLootGenerator.kt
@@ -0,0 +1,97 @@
+/***********************************************************************************
+ * Copyright (c) 2019 /// 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.services.gameplay.combat.loot.generation
+
+import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent
+import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData
+import com.projectswg.holocore.resources.support.objects.ObjectCreator
+import com.projectswg.holocore.resources.support.objects.StaticItemCreator
+import com.projectswg.holocore.resources.support.objects.swg.SWGObject
+import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
+import me.joshlarson.jlcommon.control.IntentChain
+import java.util.concurrent.ThreadLocalRandom
+
+class ItemLootGenerator {
+
+ fun generate(corpse: CreatureObject, killer: CreatureObject, loot: MutableList, lootTables: List) {
+ val random = ThreadLocalRandom.current()
+
+ val tableRoll = random.nextInt(100) + 1
+
+ // Admin Variables
+ val admin = killer.hasCommand("admin")
+ val adminOutput1 = StringBuilder("$tableRoll //")
+ val adminOutput2 = StringBuilder()
+
+ val lootTableLookup = ServerData.lootTables
+ for ((tableId, npcTable) in lootTables.withIndex()) {
+ val tableChance = npcTable.chance
+
+ if (tableRoll > tableChance) {
+ // Skip ahead if there's no drop chance
+ adminOutput1.append("/ \\#FF0000 loot_table").append(tableId+1)
+ break
+ }
+ adminOutput1.append("/ \\#00FF00 loot_table").append(tableId+1)
+
+ val groupRoll = random.nextInt(100) + 1
+ val lootTableItem = lootTableLookup.getLootTableItem(npcTable.lootTable, corpse.difficulty.name, corpse.level.toInt()) ?: continue
+
+ for ((chance, items) in lootTableItem.groups) { // group of items to be granted
+ if (groupRoll > chance)
+ continue
+
+ val itemName = items[random.nextInt(items.size)]
+
+ adminOutput2.append(itemName).append('\n')
+ when {
+ itemName.startsWith("dynamic_") -> { // TODO dynamic item handling
+ SystemMessageIntent(killer.owner!!, "We don't support this loot item yet: $itemName").broadcast()
+ }
+ itemName.endsWith(".iff") -> {
+ loot.add(ObjectCreator.createObjectFromTemplate(itemName))
+ }
+ else -> {
+ val obj = StaticItemCreator.createItem(itemName)
+ if (obj != null)
+ loot.add(obj)
+ }
+ }
+
+ break
+ }
+ }
+ if (admin) {
+ IntentChain.broadcastChain(
+ SystemMessageIntent(killer.owner!!, adminOutput1.toString()),
+ SystemMessageIntent(killer.owner!!, adminOutput2.toString())
+ )
+ }
+ }
+
+}
diff --git a/src/test/java/com/projectswg/holocore/services/gameplay/combat/loot/TestRareLootService.java b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/NPCLootTable.kt
similarity index 57%
rename from src/test/java/com/projectswg/holocore/services/gameplay/combat/loot/TestRareLootService.java
rename to src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/NPCLootTable.kt
index 4cbb4427c..ac88ff7d3 100644
--- a/src/test/java/com/projectswg/holocore/services/gameplay/combat/loot/TestRareLootService.java
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/NPCLootTable.kt
@@ -1,5 +1,5 @@
/***********************************************************************************
- * Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
+ * Copyright (c) 2019 /// 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. *
@@ -24,50 +24,7 @@
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see . *
***********************************************************************************/
-package com.projectswg.holocore.services.gameplay.combat.loot;
-import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty;
-import com.projectswg.holocore.test.runners.TestRunnerNoIntents;
-import org.junit.Assert;
-import org.junit.Test;
+package com.projectswg.holocore.services.gameplay.combat.loot.generation
-public class TestRareLootService extends TestRunnerNoIntents {
-
- private final RareLootService rls;
-
- public TestRareLootService() {
- rls = new RareLootService();
- }
-
- @Test
- public void testIsPlayerEligible() {
- Assert.assertTrue(rls.isPlayerEligible(true, false));
- Assert.assertFalse(rls.isPlayerEligible(false, true));
- Assert.assertFalse(rls.isPlayerEligible(false, false));
- }
-
- @Test
- public void testIsLevelEligible() {
- Assert.assertTrue(rls.isLevelEligible(90, 84));
- Assert.assertFalse(rls.isLevelEligible(90, 83));
- }
-
- @Test
- public void testIsDrop() {
- Assert.assertTrue(rls.isDrop(1));
- Assert.assertFalse(rls.isDrop(2));
- }
-
- @Test
- public void testTemplateForDifficulty() {
- Assert.assertEquals("object/tangible/item/shared_rare_loot_chest_1.iff", rls.templateForDifficulty(CreatureDifficulty.NORMAL));
- Assert.assertEquals("object/tangible/item/shared_rare_loot_chest_2.iff", rls.templateForDifficulty(CreatureDifficulty.ELITE));
- Assert.assertEquals("object/tangible/item/shared_rare_loot_chest_3.iff", rls.templateForDifficulty(CreatureDifficulty.BOSS));
- }
-
- @Test
- public void testChestIdForTemplate() {
- Assert.assertEquals("rare_loot_chest_1", rls.chestIdForTemplate("object/tangible/item/shared_rare_loot_chest_1.iff"));
- }
-
-}
+data class NPCLootTable(val chance: Int, val lootTable: String)
diff --git a/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/RareItemLootGenerator.kt b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/RareItemLootGenerator.kt
new file mode 100644
index 000000000..8a8b3fbf5
--- /dev/null
+++ b/src/main/java/com/projectswg/holocore/services/gameplay/combat/loot/generation/RareItemLootGenerator.kt
@@ -0,0 +1,80 @@
+/***********************************************************************************
+ * Copyright (c) 2019 /// 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.services.gameplay.combat.loot.generation
+
+import com.projectswg.common.data.encodables.oob.StringId
+import com.projectswg.holocore.resources.support.objects.ObjectCreator
+import com.projectswg.holocore.resources.support.objects.swg.SWGObject
+import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty
+import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
+import com.projectswg.holocore.services.gameplay.combat.loot.RareLootService.*
+import me.joshlarson.jlcommon.log.Log
+import java.util.concurrent.ThreadLocalRandom
+import kotlin.math.abs
+
+class RareItemLootGenerator {
+
+ fun generate(corpse: CreatureObject, killer: CreatureObject, loot: MutableList) {
+ if (abs(corpse.level.toInt() - killer.level.toInt()) > MAX_LEVEL_DIFFERENCE) // Levels can't be too far apart
+ return
+
+ val roll = 0 //ThreadLocalRandom.current().nextInt(100) + 1 // Rolls from 0 to 99, then we add 1 and it becomes 1 to 100
+ if (roll >= DROP_CHANCE) {
+ Log.d("No RLS drop from %s with roll %d", corpse, roll)
+ return
+ }
+
+ val template = templateForDifficulty(corpse.difficulty)
+ val chest = ObjectCreator.createObjectFromTemplate(template)
+
+ chest.setStf("loot_n", chestIdForTemplate(template) + "_n")
+ chest.setDetailStf(StringId("loot_n", chestIdForTemplate(template) + "_d")) // Not located in loot_d, for whatever reason...
+
+ loot.add(chest)
+ }
+
+ private fun chestIdForTemplate(template: String): String {
+ return template.replace("object/tangible/item/shared_", "").replace(".iff", "")
+ }
+
+ private fun templateForDifficulty(difficulty: CreatureDifficulty): String {
+ return when (difficulty) {
+ CreatureDifficulty.NORMAL -> RARE_CHEST
+ CreatureDifficulty.ELITE -> EXCEPTIONAL_CHEST
+ CreatureDifficulty.BOSS -> LEGENDARY_CHEST
+ }
+ }
+
+ companion object {
+
+ // TODO these two could be config options
+ private const val MAX_LEVEL_DIFFERENCE: Short = 6 // +-6 difference is allowed between killer and corpse
+ private const val DROP_CHANCE = 1 // One in a hundred eligible kills will drop a chest
+
+ }
+}
diff --git a/src/main/java/com/projectswg/holocore/services/support/objects/items/StaticItemService.kt b/src/main/java/com/projectswg/holocore/services/support/objects/items/StaticItemService.kt
index ff6451e10..5acb00885 100644
--- a/src/main/java/com/projectswg/holocore/services/support/objects/items/StaticItemService.kt
+++ b/src/main/java/com/projectswg/holocore/services/support/objects/items/StaticItemService.kt
@@ -30,14 +30,9 @@ import com.projectswg.common.network.packets.swg.zone.object_controller.ShowLoot
import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent
import com.projectswg.holocore.intents.support.objects.items.CreateStaticItemIntent
import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent
-import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader
-import com.projectswg.holocore.resources.support.data.server_info.loader.StaticItemLoader
-import com.projectswg.holocore.resources.support.data.server_info.loader.StaticItemLoader.StaticItemInfo
-import com.projectswg.holocore.resources.support.objects.ObjectCreator
+import com.projectswg.holocore.resources.support.objects.StaticItemCreator
import com.projectswg.holocore.resources.support.objects.swg.SWGObject
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
-import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject
-import com.projectswg.holocore.resources.support.objects.swg.weapon.WeaponObject
import kotlinx.coroutines.*
import me.joshlarson.jlcommon.control.Intent
import me.joshlarson.jlcommon.control.IntentChain
@@ -45,7 +40,6 @@ import me.joshlarson.jlcommon.control.IntentHandler
import me.joshlarson.jlcommon.control.Service
import me.joshlarson.jlcommon.log.Log
import java.util.*
-import kotlin.math.floor
class StaticItemService : Service() {
@@ -63,7 +57,7 @@ class StaticItemService : Service() {
val objectCreationHandler = csii.objectCreationHandler
// If adding these items to the container would exceed the max capacity...
- if (!objectCreationHandler.isIgnoreVolume && container.volume + itemNames.size > container.maxContainerSize) {
+ if (!objectCreationHandler.isIgnoreVolume && container.containedObjects.size + itemNames.size > container.maxContainerSize) {
objectCreationHandler.containerFull()
return
}
@@ -71,9 +65,11 @@ class StaticItemService : Service() {
val intentChain = IntentChain()
val objects = ArrayList()
for (itemName in itemNames) {
- val obj = createItem(intentChain, itemName, container)
+ val obj = StaticItemCreator.createItem(itemName)
if (obj != null) {
objects.add(obj)
+ obj.moveToContainer(container)
+ intentChain.broadcastAfter(ObjectCreatedIntent(obj))
} else {
Log.d("%s could not be spawned because the item name is unknown", itemName)
val requesterOwner = csii.requester.owner
@@ -93,227 +89,11 @@ class StaticItemService : Service() {
}
}
- private fun createItem(intentChain: IntentChain, itemName: String, container: SWGObject): SWGObject? {
- val info = DataLoader.staticItems().getItemByName(itemName) ?: return null
- val swgObject = ObjectCreator.createObjectFromTemplate(info.iffTemplate) as? TangibleObject ?: return null
-
- applyAttributes(swgObject, info)
-
- intentChain.broadcastAfter(ObjectCreatedIntent(swgObject))
- swgObject.moveToContainer(container)
- Log.d("Successfully moved %s into container %s", itemName, container)
-
- return swgObject
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemInfo) {
- obj.addAttribute("condition", String.format("%d/%d", info.hitPoints, info.hitPoints))
- obj.addAttribute("volume", info.volume.toString())
- obj.objectName = info.stringName
-
- applyAttributes(obj, info.armorInfo)
- applyAttributes(obj, info.wearableInfo)
- applyAttributes(obj, info.weaponInfo)
- applyAttributes(obj, info.collectionInfo)
- applyAttributes(obj, info.costumeInfo)
- applyAttributes(obj, info.dnaInfo)
- applyAttributes(obj, info.grantInfo)
- applyAttributes(obj, info.genericInfo)
- applyAttributes(obj, info.objectInfo)
- applyAttributes(obj, info.schematicInfo)
- applyAttributes(obj, info.storytellerInfo)
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.ArmorItemInfo?) {
- if (info == null)
- return
-
- if (info.requiredProfession.isNotEmpty())
- obj.addAttribute("class_required", "@ui_roadmap:title_" + info.requiredProfession)
- obj.addAttribute("required_combat_level", info.requiredLevel.toString())
- val kineticMax: Int
- val energyMax: Int
- when (info.armorType) {
- StaticItemLoader.ArmorItemInfo.ArmorType.ASSAULT -> {
- kineticMax = 7000
- energyMax = 5000
- obj.addAttribute("armor_category", "@obj_attr_n:armor_assault")
- }
- StaticItemLoader.ArmorItemInfo.ArmorType.BATTLE -> {
- kineticMax = 6000
- energyMax = 6000
- obj.addAttribute("armor_category", "@obj_attr_n:armor_battle")
- }
- StaticItemLoader.ArmorItemInfo.ArmorType.RECON -> {
- kineticMax = 5000
- energyMax = 7000
- obj.addAttribute("armor_category", "@obj_attr_n:armor_reconnaissance")
- }
- }
-
- obj.addAttribute("cat_armor_standard_protection.kinetic", calculateProtection(kineticMax, info.protection))
- obj.addAttribute("cat_armor_standard_protection.energy", calculateProtection(energyMax, info.protection))
- obj.addAttribute("cat_armor_special_protection.special_protection_type_heat", calculateProtection(6000, info.protection))
- obj.addAttribute("cat_armor_special_protection.special_protection_type_cold", calculateProtection(6000, info.protection))
- obj.addAttribute("cat_armor_special_protection.special_protection_type_acid", calculateProtection(6000, info.protection))
- obj.addAttribute("cat_armor_special_protection.special_protection_type_electricity", calculateProtection(6000, info.protection))
-
- applySkillMods(obj, info.skillMods)
- applyColors(obj, info.color)
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.WearableItemInfo?) {
- if (info == null)
- return
-
- if (info.requiredProfession.isNotEmpty())
- obj.addAttribute("class_required", "@ui_roadmap:title_" + info.requiredProfession)
- obj.addAttribute("required_combat_level", info.requiredLevel.toString())
-
- if (info.requiredFaction.isNotEmpty())
- obj.addAttribute("faction_restriction", "@pvp_factions:" + info.requiredFaction)
-
- // Apply the mods!
- applySkillMods(obj, info.skillMods)
- applyColors(obj, info.color)
-
- // Add the race restrictions only if there are any
- val raceRestriction = buildRaceRestrictionString(info)
- if (raceRestriction.isNotEmpty())
- obj.addAttribute("species_restrictions.species_name", raceRestriction)
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.WeaponItemInfo?) {
- if (info == null)
- return
-
- if (info.requiredProfession.isNotEmpty())
- obj.addAttribute("class_required", "@ui_roadmap:title_" + info.requiredProfession)
- obj.addAttribute("required_combat_level", info.requiredLevel.toString())
- obj.addAttribute("cat_wpn_damage.wpn_damage_type", "@obj_attr_n:${info.damageType.name.toLowerCase(Locale.US)}")
- obj.addAttribute("cat_wpn_damage.wpn_category", "@obj_attr_n:wpn_category_" + info.weaponType.num)
- obj.addAttribute("cat_wpn_damage.wpn_attack_speed", (info.attackSpeed / 100).toString())
- obj.addAttribute("cat_wpn_damage.damage", "${info.minDamage}-${info.maxDamage}")
- if (info.elementalType != null) { // Not all weapons have elemental damage.
- obj.addAttribute("cat_wpn_damage.wpn_elemental_type", "@obj_attr_n:${info.elementalType.name.toLowerCase(Locale.US)}")
- obj.addAttribute("cat_wpn_damage.wpn_elemental_value", info.elementalDamage.toString())
- }
-
- obj.addAttribute("cat_wpn_damage.weapon_dps", info.actualDps.toString())
-
- if (info.procEffect.isNotEmpty())
- // Not all weapons have a proc effect
- obj.addAttribute("proc_name", info.procEffect)
-
- // TODO set DPS
-
- obj.addAttribute("cat_wpn_other.wpn_range", String.format("%d-%dm", info.minRange, info.maxRange))
- // Ziggy: Special Action Cost would go under cat_wpn_other as well, but it's a pre-NGE artifact.
-
- val weapon = obj as WeaponObject
- weapon.type = info.weaponType
- weapon.attackSpeed = info.attackSpeed.toFloat()
- weapon.minRange = info.minRange.toFloat()
- weapon.maxRange = info.maxRange.toFloat()
- weapon.damageType = info.damageType
- weapon.elementalType = info.elementalType
- weapon.minDamage = info.minDamage
- weapon.maxDamage = info.maxDamage
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.CollectionItemInfo?) {
- if (info == null)
- return
-
- obj.addAttribute("collection_name", info.slotName)
-
- applyColors(obj, info.color)
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.CostumeItemInfo?) {
-// if (info == null)
-// return;
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.DnaItemInfo?) {
-// if (info == null)
-// return;
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.GrantItemInfo?) {
-// if (info == null)
-// return;
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.GenericItemInfo?) {
- if (info == null)
- return
-
- if (info.value != 0)
- obj.addAttribute("charges", info.value.toString())
-
- applyColors(obj, info.color)
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.ObjectItemInfo?) {
- if (info == null)
- return
-
- if (info.value != 0)
- obj.addAttribute("charges", info.value.toString())
-
- applyColors(obj, info.color)
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.SchematicItemInfo?) {
-// if (info == null)
-// return;
- }
-
- private fun applyAttributes(obj: TangibleObject, info: StaticItemLoader.StorytellerItemInfo?) {
-// if (info == null)
-// return;
- }
-
- private fun applySkillMods(obj: TangibleObject, skillMods: Map) {
- for ((key, value) in skillMods)
- obj.addAttribute(key, value.toString())
- }
-
- private fun applyColors(obj: TangibleObject, colors: IntArray) {
- for (i in 0..3) {
- if (colors[i] >= 0) {
- obj.putCustomization("/private/index_color_$i", colors[i])
- }
- }
- }
-
- private fun buildRaceRestrictionString(info: StaticItemLoader.WearableItemInfo): String {
- var races = ""
-
- if (info.isRaceWookie)
- races += "Wookiee "
- if (info.isRaceIthorian)
- races += "Ithorian "
- if (info.isRaceRodian)
- races += "Rodian "
- if (info.isRaceTrandoshan)
- races += "Trandoshan "
- if (info.isRaceRest)
- races += "MonCal Human Zabrak Bothan Sullustan Twi'lek "
-
- return if (races.isEmpty()) "" else races.substring(0, races.length - 1)
- }
-
- private fun calculateProtection(max: Int, protection: Double): String {
- return floor(max * protection).toInt().toString()
- }
-
interface ObjectCreationHandler {
val isIgnoreVolume: Boolean
fun success(createdObjects: List)
- open fun containerFull() {
+ fun containerFull() {
}
}