From 5c5c68742b6ee8d645a110c8b9f0be87592c774a Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 14 Jan 2023 10:50:36 +0700 Subject: [PATCH] Add docs to MatteryPlayerCapability --- .../otm/capability/MatteryPlayerCapability.kt | 185 ++++++++++++++++-- .../mc/otm/capability/UUIDIntModifiersMap.kt | 5 + 2 files changed, 172 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index 7b6b7a843..a4cba3302 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -79,15 +79,34 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial /** * For fields that need to be synchronized only to owning player + * + * Please mind if you really need to use this in your mod; + * don't forget to specify field names when you add them to synchronizer. + * + * Even if other side does not have your field defined, both sides will negotiate + * and figure out how to deal with this situation as long as you specify field name. */ val synchronizer = FieldSynchronizer() /** * For fields that need to be synchronized to everyone + * + * Please mind if you really need to use this in your mod; + * don't forget to specify field names when you add them to synchronizer. + * + * Even if other side does not have your field defined, both sides will negotiate + * and figure out how to deal with this situation as long as you specify field name. */ val publicSynchronizer = FieldSynchronizer() + /** + * Whenever player has Exopack + */ var hasExoPack by publicSynchronizer.bool(name = "hasExoPack") + + /** + * Whenever to render Exopack on player + */ var displayExoPack by publicSynchronizer.bool(true, name = "displayExoPack") private val exoPackSlotModifierMap: MutableMap by synchronizer.Map( @@ -100,6 +119,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial name = "exoPackSlotModifierMap" ) + /** + * Modifier map for Exopack slot counts + * + * If you want to properly extend Exopack suit capacity, add your value into this map + */ val exoPackSlotModifier = UUIDIntModifiersMap(observer = observer@{ if (ply !is ServerPlayer) return@observer @@ -111,6 +135,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } }, backingMap = this.exoPackSlotModifierMap) + /** + * Current slot count of Exopack + * + * For properly affecting this value please look at [exoPackSlotModifier] + */ var exoPackSlotCount by publicSynchronizer.int(setter = setter@{ value, access, _ -> require(value >= 0) { "Invalid slot count $value" } @@ -121,6 +150,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } }, name = "exoPackSlotCount") + /** + * Exopack container, which actually store items inside Exopack + */ var exoPackContainer: MatteryContainer = PlayerMatteryContainer(0) private set(value) { _exoPackMenu = null @@ -143,6 +175,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial field = value } + /** + * Whenever Exopack has 3x3 crafting grid upgrade installed + */ var isExoPackCraftingUpgraded by publicSynchronizer.bool(setter = setter@{ value, access, _ -> if (value != access.read()) { access.write(value) @@ -156,6 +191,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial field = value } + /** + * [ExoPackInventoryMenu] which player will see when pressing inventory key + */ val exoPackMenu: ExoPackInventoryMenu get() { if (_exoPackMenu == null) { @@ -166,6 +204,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } private var shouldSendIteration = false + + /** + * Android' iteration counter (death counter, updating only when Android) + */ var iteration = 0 private set @@ -186,15 +228,41 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial // getting them unburied will be a very work intense task private val trackingPlayers = Reference2ObjectOpenHashMap() + /** + * This returns if player is an Android or will become one on death/sleep/etc + */ val isEverAndroid: Boolean get() = isAndroid || willBecomeAndroid - var lastJumpTicks = 14 + internal var lastJumpTicks = 14 + /** + * In-game ticks this player exists (server play time, excluding time while "dead"), used by various things across the mod, such as + * "play time chance" loot condition + */ var ticksIExist = 0 private set + /** + * Whenever player should become an Android once transformation conditions are met (e.g. player dies or sleeps in bed) + */ var willBecomeAndroid by publicSynchronizer.bool(name = "willBecomeAndroid") + + /** + * Whenever player is an Android + * + * In OTM itself, Android players are generally treated as something in between alive and undead, such as: + * * They can't be healed using potions, can't receive regeneration buffs + * * But they can be harmed using harm potion + * * Can't be poisoned (with default datapack) + * * CAN be withered (with default datapack) + * * etc + * + * Android-immune (de)buffs are specified in `data/overdrive_that_matters/tags/mob_effect/android_immune_effects.json` + */ var isAndroid by publicSynchronizer.bool(name = "isAndroid") + /** + * [IMatteryEnergyStorage] instance, representing Android' battery charge + */ val androidEnergy = AndroidPowerSource(ply, synchronizer, ServerConfig.ANDROID_MAX_ENERGY, ServerConfig.ANDROID_MAX_ENERGY) fun invalidateNetworkState() { @@ -214,12 +282,29 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } + /** + * Flags player to turn into Android + * + * This is used by Android Pill item in-game + * + * Does nothing if player is already an Android + */ fun becomeAndroidSoft() { if (isAndroid || willBecomeAndroid) return willBecomeAndroid = true (ply as? ServerPlayer)?.displayClientMessage(TranslatableComponent("otm.pill.message").withStyle(ChatFormatting.GRAY), false) } + /** + * Unconditionally and instantly turns player into Android + * + * This is different than setting [isAndroid] because it setup some variables, + * such as battery charge + * + * Does not kill the player + * + * Does nothing if player is already an Android + */ fun becomeAndroid() { if (isAndroid) return @@ -236,6 +321,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } + /** + * [becomeAndroid] plus instantly kills the player + * + * Does nothing if player is already an Android + */ fun becomeAndroidAndKill() { if (isAndroid) return @@ -243,6 +333,25 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial ply.hurt(MRegistry.DAMAGE_BECOME_ANDROID, ply.maxHealth * 2) } + /** + * Unconditionally and instantly turns player into "human" + * + * This is different than setting [isAndroid] because it setup some variables, + * such as battery charge + * + * Does not kill the player + * + * Drops equipped battery BUT does NOT refund research, nor does remove features; + * Once player becomes Android again, they will retain all features and research. + * + * If you need to also remove research and features, look at [obliviate] + * + * However, this does reset [iteration] and death log + * + * Does nothing if player is not an Android + * + * Unsets [willBecomeAndroid] flag if it is true + */ fun becomeHumane() { if (willBecomeAndroid) willBecomeAndroid = false if (!isAndroid) return @@ -258,8 +367,19 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial if (ply is ServerPlayer) { BecomeHumaneTrigger.trigger(ply) } + + for (feature in featureMap.values) { + feature.removeModifiers() + } } + /** + * [becomeHumane] plus kills the player + * + * Does nothing if player is not an Android + * + * Unsets [willBecomeAndroid] flag if it is true + */ fun becomeHumaneAndKill() { if (willBecomeAndroid) willBecomeAndroid = false if (!isAndroid) return @@ -268,6 +388,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial ply.hurt(MRegistry.DAMAGE_BECOME_HUMANE, ply.maxHealth * 2) } + /** + * *Refunds* all research and removes all Android features + * + * Resets [iteration] and death log + */ fun obliviate(refund: Boolean = true) { for (instance in research.values) { if (instance.isResearched) { @@ -289,13 +414,16 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial deathLog.clear() } + /** + * Returns [AndroidResearch] state for specified [AndroidResearchType] + */ fun getResearch(type: AndroidResearchType): AndroidResearch { return research.computeIfAbsent(type) { return@computeIfAbsent AndroidResearch(type, this) } } - fun reloadResearch() { + internal fun reloadResearch() { val old = ArrayList(research.size) old.addAll(research.values) research.clear() @@ -309,6 +437,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } + /** + * Android features stream, use this to get list of all Android features this player has + */ val features: Stream get() = featureMap.values.stream() private fun addFeature(feature: AndroidFeature): Boolean { @@ -331,6 +462,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial return true } + /** + * Adds or returns specified Android feature to this player + */ @Suppress("unchecked_cast") fun addFeature(feature: AndroidFeatureType): T { val get = featureMap[feature] @@ -340,7 +474,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial featureMap[feature] = factory - if (!ply.level.isClientSide) { + if (!ply.level.isClientSide && isAndroid) { queuedTicks.add(factory::applyModifiers) } @@ -356,14 +490,17 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial return factory } + /** + * Removes specified Android feature from this player + * + * @return whenever feature was found and removed + */ fun removeFeature(feature: AndroidFeatureType<*>): Boolean { val removed = featureMap.remove(feature) if (removed != null) { - if (!ply.level.isClientSide) { - queuedTicks.add { - removed.removeModifiers() - } + if (!ply.level.isClientSide && isAndroid) { + queuedTicks.add(removed::removeModifiers) } if (ply is ServerPlayer) { @@ -374,21 +511,32 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial return removed != null } + /** + * Whenever player has specified [feature] + */ fun hasFeature(feature: AndroidFeatureType<*>): Boolean { return featureMap.containsKey(feature) } + /** + * Whenever player has specified [feature] and its [AndroidFeature.level] is equal to or greater than [level] + */ fun hasFeatureLevel(feature: AndroidFeatureType<*>, level: Int): Boolean { val get = featureMap[feature] ?: return false return get.level >= level } + /** + * Raw get of Android feature of this player + * + * @return null if no such feature is present + */ @Suppress("unchecked_cast") fun getFeature(feature: AndroidFeatureType): T? { return featureMap[feature] as T? } - fun onHurt(event: LivingHurtEvent) { + internal fun onHurt(event: LivingHurtEvent) { if (isAndroid) { for (feature in featureMap.values) { feature.onHurt(event) @@ -400,16 +548,14 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - fun computeIfAbsent(feature: AndroidFeatureType): T { + internal fun computeIfAbsent(feature: AndroidFeatureType): T { return getFeature(feature) ?: addFeature(feature) } - fun getFeatureO(feature: AndroidFeatureType): Optional { - val get = getFeature(feature) - return if (get != null) Optional.of(get) else Optional.empty() - } - - fun ifFeature(feature: AndroidFeatureType, consumer: (T) -> Unit) { + /** + * Lambda style of [getFeature] + */ + inline fun ifFeature(feature: AndroidFeatureType, consumer: (T) -> Unit) { val get = getFeature(feature) if (get != null) { @@ -543,6 +689,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } + /** + * Drops Android battery as in-world item + */ fun dropBattery() { if (androidEnergy.item.isEmpty) return ply.spawnAtLocation(androidEnergy.item) @@ -569,7 +718,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - fun tickClient() { + private fun tickClient() { queuedTicks.clear() if (!ply.isAlive) { @@ -585,7 +734,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial tickInventory() } - fun preTick() { + private fun preTick() { if (!ply.isAlive) return if (isAndroid) { @@ -596,7 +745,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } } - fun tick() { + private fun tick() { if (!ply.isAlive) return ticksIExist++ diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UUIDIntModifiersMap.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UUIDIntModifiersMap.kt index 8ddafb76a..772bd47f5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UUIDIntModifiersMap.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/UUIDIntModifiersMap.kt @@ -7,6 +7,11 @@ import net.minecraftforge.common.util.INBTSerializable import ru.dbotthepony.mc.otm.core.contains import java.util.UUID +/** + * This class represents an `UUID -> Int` map, with each `Int` value being added to [value] property + * + * This is like Minecraft's Attribute map, except it operate only on whole numbers + */ class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backingMap: MutableMap = HashMap()) : INBTSerializable { var value: Int = 0 private set