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
*
* 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<UUID, Int> 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<ServerPlayer, FieldSynchronizer.Endpoint>()
/**
* 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<AndroidResearch>(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<out AndroidFeature> 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 <T : AndroidFeature> addFeature(feature: AndroidFeatureType<T>): 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 <T : AndroidFeature> getFeature(feature: AndroidFeatureType<T>): 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 <T : AndroidFeature> computeIfAbsent(feature: AndroidFeatureType<T>): T {
internal fun <T : AndroidFeature> computeIfAbsent(feature: AndroidFeatureType<T>): T {
return getFeature(feature) ?: addFeature(feature)
}
fun <T : AndroidFeature> getFeatureO(feature: AndroidFeatureType<T>): Optional<T> {
val get = getFeature(feature)
return if (get != null) Optional.of(get) else Optional.empty()
}
fun <T : AndroidFeature> ifFeature(feature: AndroidFeatureType<T>, consumer: (T) -> Unit) {
/**
* Lambda style of [getFeature]
*/
inline fun <T : AndroidFeature> ifFeature(feature: AndroidFeatureType<T>, 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++

View File

@ -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<UUID, Int> = HashMap()) : INBTSerializable<ListTag> {
var value: Int = 0
private set