Add docs to MatteryPlayerCapability

This commit is contained in:
DBotThePony 2023-01-14 10:50:36 +07:00
parent fd7619b144
commit 5c5c68742b
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 172 additions and 18 deletions

View File

@ -79,15 +79,34 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
/** /**
* For fields that need to be synchronized only to owning player * 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() val synchronizer = FieldSynchronizer()
/** /**
* For fields that need to be synchronized to everyone * 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() val publicSynchronizer = FieldSynchronizer()
/**
* Whenever player has Exopack
*/
var hasExoPack by publicSynchronizer.bool(name = "hasExoPack") var hasExoPack by publicSynchronizer.bool(name = "hasExoPack")
/**
* Whenever to render Exopack on player
*/
var displayExoPack by publicSynchronizer.bool(true, name = "displayExoPack") var displayExoPack by publicSynchronizer.bool(true, name = "displayExoPack")
private val exoPackSlotModifierMap: MutableMap<UUID, Int> by synchronizer.Map( private val exoPackSlotModifierMap: MutableMap<UUID, Int> by synchronizer.Map(
@ -100,6 +119,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
name = "exoPackSlotModifierMap" 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@{ val exoPackSlotModifier = UUIDIntModifiersMap(observer = observer@{
if (ply !is ServerPlayer) if (ply !is ServerPlayer)
return@observer return@observer
@ -111,6 +135,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
}, backingMap = this.exoPackSlotModifierMap) }, 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, _ -> var exoPackSlotCount by publicSynchronizer.int(setter = setter@{ value, access, _ ->
require(value >= 0) { "Invalid slot count $value" } require(value >= 0) { "Invalid slot count $value" }
@ -121,6 +150,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
}, name = "exoPackSlotCount") }, name = "exoPackSlotCount")
/**
* Exopack container, which actually store items inside Exopack
*/
var exoPackContainer: MatteryContainer = PlayerMatteryContainer(0) var exoPackContainer: MatteryContainer = PlayerMatteryContainer(0)
private set(value) { private set(value) {
_exoPackMenu = null _exoPackMenu = null
@ -143,6 +175,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
field = value field = value
} }
/**
* Whenever Exopack has 3x3 crafting grid upgrade installed
*/
var isExoPackCraftingUpgraded by publicSynchronizer.bool(setter = setter@{ value, access, _ -> var isExoPackCraftingUpgraded by publicSynchronizer.bool(setter = setter@{ value, access, _ ->
if (value != access.read()) { if (value != access.read()) {
access.write(value) access.write(value)
@ -156,6 +191,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
field = value field = value
} }
/**
* [ExoPackInventoryMenu] which player will see when pressing inventory key
*/
val exoPackMenu: ExoPackInventoryMenu val exoPackMenu: ExoPackInventoryMenu
get() { get() {
if (_exoPackMenu == null) { if (_exoPackMenu == null) {
@ -166,6 +204,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
private var shouldSendIteration = false private var shouldSendIteration = false
/**
* Android' iteration counter (death counter, updating only when Android)
*/
var iteration = 0 var iteration = 0
private set private set
@ -186,15 +228,41 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
// getting them unburied will be a very work intense task // getting them unburied will be a very work intense task
private val trackingPlayers = Reference2ObjectOpenHashMap<ServerPlayer, FieldSynchronizer.Endpoint>() private val trackingPlayers = Reference2ObjectOpenHashMap<ServerPlayer, FieldSynchronizer.Endpoint>()
/**
* This returns if player is an Android or will become one on death/sleep/etc
*/
val isEverAndroid: Boolean get() = isAndroid || willBecomeAndroid 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 var ticksIExist = 0
private set 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") 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") 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) val androidEnergy = AndroidPowerSource(ply, synchronizer, ServerConfig.ANDROID_MAX_ENERGY, ServerConfig.ANDROID_MAX_ENERGY)
fun invalidateNetworkState() { 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() { fun becomeAndroidSoft() {
if (isAndroid || willBecomeAndroid) return if (isAndroid || willBecomeAndroid) return
willBecomeAndroid = true willBecomeAndroid = true
(ply as? ServerPlayer)?.displayClientMessage(TranslatableComponent("otm.pill.message").withStyle(ChatFormatting.GRAY), false) (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() { fun becomeAndroid() {
if (isAndroid) return 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() { fun becomeAndroidAndKill() {
if (isAndroid) return if (isAndroid) return
@ -243,6 +333,25 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
ply.hurt(MRegistry.DAMAGE_BECOME_ANDROID, ply.maxHealth * 2) 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() { fun becomeHumane() {
if (willBecomeAndroid) willBecomeAndroid = false if (willBecomeAndroid) willBecomeAndroid = false
if (!isAndroid) return if (!isAndroid) return
@ -258,8 +367,19 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
if (ply is ServerPlayer) { if (ply is ServerPlayer) {
BecomeHumaneTrigger.trigger(ply) 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() { fun becomeHumaneAndKill() {
if (willBecomeAndroid) willBecomeAndroid = false if (willBecomeAndroid) willBecomeAndroid = false
if (!isAndroid) return if (!isAndroid) return
@ -268,6 +388,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
ply.hurt(MRegistry.DAMAGE_BECOME_HUMANE, ply.maxHealth * 2) 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) { fun obliviate(refund: Boolean = true) {
for (instance in research.values) { for (instance in research.values) {
if (instance.isResearched) { if (instance.isResearched) {
@ -289,13 +414,16 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
deathLog.clear() deathLog.clear()
} }
/**
* Returns [AndroidResearch] state for specified [AndroidResearchType]
*/
fun getResearch(type: AndroidResearchType): AndroidResearch { fun getResearch(type: AndroidResearchType): AndroidResearch {
return research.computeIfAbsent(type) { return research.computeIfAbsent(type) {
return@computeIfAbsent AndroidResearch(type, this) return@computeIfAbsent AndroidResearch(type, this)
} }
} }
fun reloadResearch() { internal fun reloadResearch() {
val old = ArrayList<AndroidResearch>(research.size) val old = ArrayList<AndroidResearch>(research.size)
old.addAll(research.values) old.addAll(research.values)
research.clear() 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<out AndroidFeature> get() = featureMap.values.stream() val features: Stream<out AndroidFeature> get() = featureMap.values.stream()
private fun addFeature(feature: AndroidFeature): Boolean { private fun addFeature(feature: AndroidFeature): Boolean {
@ -331,6 +462,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
return true return true
} }
/**
* Adds or returns specified Android feature to this player
*/
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
fun <T : AndroidFeature> addFeature(feature: AndroidFeatureType<T>): T { fun <T : AndroidFeature> addFeature(feature: AndroidFeatureType<T>): T {
val get = featureMap[feature] val get = featureMap[feature]
@ -340,7 +474,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
featureMap[feature] = factory featureMap[feature] = factory
if (!ply.level.isClientSide) { if (!ply.level.isClientSide && isAndroid) {
queuedTicks.add(factory::applyModifiers) queuedTicks.add(factory::applyModifiers)
} }
@ -356,14 +490,17 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
return factory return factory
} }
/**
* Removes specified Android feature from this player
*
* @return whenever feature was found and removed
*/
fun removeFeature(feature: AndroidFeatureType<*>): Boolean { fun removeFeature(feature: AndroidFeatureType<*>): Boolean {
val removed = featureMap.remove(feature) val removed = featureMap.remove(feature)
if (removed != null) { if (removed != null) {
if (!ply.level.isClientSide) { if (!ply.level.isClientSide && isAndroid) {
queuedTicks.add { queuedTicks.add(removed::removeModifiers)
removed.removeModifiers()
}
} }
if (ply is ServerPlayer) { if (ply is ServerPlayer) {
@ -374,21 +511,32 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
return removed != null return removed != null
} }
/**
* Whenever player has specified [feature]
*/
fun hasFeature(feature: AndroidFeatureType<*>): Boolean { fun hasFeature(feature: AndroidFeatureType<*>): Boolean {
return featureMap.containsKey(feature) 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 { fun hasFeatureLevel(feature: AndroidFeatureType<*>, level: Int): Boolean {
val get = featureMap[feature] ?: return false val get = featureMap[feature] ?: return false
return get.level >= level return get.level >= level
} }
/**
* Raw get of Android feature of this player
*
* @return null if no such feature is present
*/
@Suppress("unchecked_cast") @Suppress("unchecked_cast")
fun <T : AndroidFeature> getFeature(feature: AndroidFeatureType<T>): T? { fun <T : AndroidFeature> getFeature(feature: AndroidFeatureType<T>): T? {
return featureMap[feature] as T? return featureMap[feature] as T?
} }
fun onHurt(event: LivingHurtEvent) { internal fun onHurt(event: LivingHurtEvent) {
if (isAndroid) { if (isAndroid) {
for (feature in featureMap.values) { for (feature in featureMap.values) {
feature.onHurt(event) feature.onHurt(event)
@ -400,16 +548,14 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
} }
fun <T : AndroidFeature> computeIfAbsent(feature: AndroidFeatureType<T>): T { internal fun <T : AndroidFeature> computeIfAbsent(feature: AndroidFeatureType<T>): T {
return getFeature(feature) ?: addFeature(feature) return getFeature(feature) ?: addFeature(feature)
} }
fun <T : AndroidFeature> getFeatureO(feature: AndroidFeatureType<T>): Optional<T> { /**
val get = getFeature(feature) * Lambda style of [getFeature]
return if (get != null) Optional.of(get) else Optional.empty() */
} inline fun <T : AndroidFeature> ifFeature(feature: AndroidFeatureType<T>, consumer: (T) -> Unit) {
fun <T : AndroidFeature> ifFeature(feature: AndroidFeatureType<T>, consumer: (T) -> Unit) {
val get = getFeature(feature) val get = getFeature(feature)
if (get != null) { if (get != null) {
@ -543,6 +689,9 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
} }
/**
* Drops Android battery as in-world item
*/
fun dropBattery() { fun dropBattery() {
if (androidEnergy.item.isEmpty) return if (androidEnergy.item.isEmpty) return
ply.spawnAtLocation(androidEnergy.item) ply.spawnAtLocation(androidEnergy.item)
@ -569,7 +718,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
} }
fun tickClient() { private fun tickClient() {
queuedTicks.clear() queuedTicks.clear()
if (!ply.isAlive) { if (!ply.isAlive) {
@ -585,7 +734,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
tickInventory() tickInventory()
} }
fun preTick() { private fun preTick() {
if (!ply.isAlive) return if (!ply.isAlive) return
if (isAndroid) { if (isAndroid) {
@ -596,7 +745,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
} }
} }
fun tick() { private fun tick() {
if (!ply.isAlive) return if (!ply.isAlive) return
ticksIExist++ ticksIExist++

View File

@ -7,6 +7,11 @@ import net.minecraftforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.core.contains import ru.dbotthepony.mc.otm.core.contains
import java.util.UUID 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<UUID, Int> = HashMap()) : INBTSerializable<ListTag> { class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backingMap: MutableMap<UUID, Int> = HashMap()) : INBTSerializable<ListTag> {
var value: Int = 0 var value: Int = 0
private set private set