diff options
author | Lorenz <ESs95s3P5z8Pheb> | 2022-07-09 04:18:07 +0200 |
---|---|---|
committer | Lorenz <ESs95s3P5z8Pheb> | 2022-07-09 04:18:07 +0200 |
commit | a193bdca479ae7531da6f034c597335918b52699 (patch) | |
tree | bf8d56f262842d243a2628befc3360801edda8a5 | |
parent | f842a84e3aae31e5fe84b52f23550efca526f6aa (diff) | |
download | skyhanni-a193bdca479ae7531da6f034c597335918b52699.tar.gz skyhanni-a193bdca479ae7531da6f034c597335918b52699.tar.bz2 skyhanni-a193bdca479ae7531da6f034c597335918b52699.zip |
added dungeon boss damage indicator
9 files changed, 566 insertions, 3 deletions
diff --git a/src/main/java/at/lorenz/mod/LorenzMod.java b/src/main/java/at/lorenz/mod/LorenzMod.java index 39da77c6a..5a7ddf5fd 100644 --- a/src/main/java/at/lorenz/mod/LorenzMod.java +++ b/src/main/java/at/lorenz/mod/LorenzMod.java @@ -9,6 +9,7 @@ import at.lorenz.mod.config.Features; import at.lorenz.mod.dungeon.DungeonChatFilter; import at.lorenz.mod.dungeon.DungeonData; import at.lorenz.mod.dungeon.DungeonHighlightClickedBlocks; +import at.lorenz.mod.dungeon.damageindicator.DungeonBossDamageIndicator; import at.lorenz.mod.misc.*; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -59,6 +60,7 @@ public class LorenzMod { MinecraftForge.EVENT_BUS.register(new ItemDisplayOverlayFeatures()); MinecraftForge.EVENT_BUS.register(new CurrentPetDisplay()); MinecraftForge.EVENT_BUS.register(new ExpBottleOnGroundHider()); + MinecraftForge.EVENT_BUS.register(new DungeonBossDamageIndicator()); Commands.init(); diff --git a/src/main/java/at/lorenz/mod/config/Features.java b/src/main/java/at/lorenz/mod/config/Features.java index 327aa2bcb..d4c1e6014 100644 --- a/src/main/java/at/lorenz/mod/config/Features.java +++ b/src/main/java/at/lorenz/mod/config/Features.java @@ -116,6 +116,11 @@ public class Features { @ConfigOption(name = "Clicked Blocks", desc = "Highlight the following blocks when clicked in dungeon: Lever, Chest, Wither Essence") @ConfigEditorBoolean public boolean highlightClickedBlocks = false; + + @Expose + @ConfigOption(name = "Boss Damage Indicator", desc = "Shows the missing health of a boss in the dungeon and the cooldown time until the boss becomes attackable.") + @ConfigEditorBoolean + public boolean bossDamageIndicator = false; } public static class Inventory { diff --git a/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt b/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt index d14183d99..870bcaa50 100644 --- a/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt +++ b/src/main/java/at/lorenz/mod/dungeon/DungeonData.kt @@ -9,7 +9,19 @@ import net.minecraftforge.fml.common.gameevent.TickEvent class DungeonData { - var dungeonFloor: String? = null + companion object { + var dungeonFloor: String? = null + + fun isOneOf(vararg floors: String): Boolean { + for (floor in floors) { + if (dungeonFloor == floor) { + return true + } + } + + return false + } + } @SubscribeEvent fun onTick(event: TickEvent.ClientTickEvent) { diff --git a/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossDamageIndicator.kt b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossDamageIndicator.kt new file mode 100644 index 000000000..47e0d7f52 --- /dev/null +++ b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossDamageIndicator.kt @@ -0,0 +1,154 @@ +package at.lorenz.mod.dungeon.damageindicator + +import at.lorenz.mod.LorenzMod +import at.lorenz.mod.events.DungeonEnterEvent +import at.lorenz.mod.events.LorenzChatEvent +import at.lorenz.mod.utils.LorenzColor +import at.lorenz.mod.utils.LorenzUtils +import at.lorenz.mod.utils.LorenzUtils.baseMaxHealth +import at.lorenz.mod.utils.NumberUtil +import at.lorenz.mod.utils.RenderUtils +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.entity.EntityLivingBase +import net.minecraft.util.Vec3 +import net.minecraftforge.client.event.RenderLivingEvent +import net.minecraftforge.client.event.RenderWorldLastEvent +import net.minecraftforge.event.entity.EntityJoinWorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.text.DecimalFormat +import java.util.* +import kotlin.math.max + +class DungeonBossDamageIndicator { + + var data = mutableMapOf<EntityLivingBase, EntityData>() + private var bossFinder: DungeonBossFinder? = null + private val decimalFormat = DecimalFormat("0.0") + + @SubscribeEvent + fun onDungeonStart(event: DungeonEnterEvent) { + bossFinder = DungeonBossFinder() + } + + @SubscribeEvent(receiveCanceled = true) + fun onChatMessage(event: LorenzChatEvent) { + if (!LorenzUtils.inDungeons) return + if (!LorenzMod.feature.dungeon.bossDamageIndicator) return + + bossFinder?.handleChat(event.message) + } + + @SubscribeEvent + fun onWorldRender(event: RenderWorldLastEvent) { + GlStateManager.disableDepth() + GlStateManager.disableCull() + + val player = Minecraft.getMinecraft().thePlayer + + for (data in data.values) { + if (System.currentTimeMillis() > data.time + 100) continue//TODO use removeIf + if (!data.ignoreBlocks) { + if (!player.canEntityBeSeen(data.entity)) continue + } + + val entity = data.entity + + var color = data.color + var text = data.text + val delayedStart = data.delayedStart + if (delayedStart != -1L) { + if (delayedStart > System.currentTimeMillis()) { + val delay = delayedStart - System.currentTimeMillis() + color = colorForTime(delay) + var d = delay * 1.0 + d /= 1000 + text = decimalFormat.format(d) + } + } + + val partialTicks = event.partialTicks + RenderUtils.drawLabel( + Vec3( + RenderUtils.interpolate(entity.posX, entity.lastTickPosX, partialTicks), + RenderUtils.interpolate(entity.posY, entity.lastTickPosY, partialTicks) + 0.5f, + RenderUtils.interpolate(entity.posZ, entity.lastTickPosZ, partialTicks) + ), + text, + color.toColor(), + partialTicks, + true, + 6f + ) + } + GlStateManager.enableDepth() + GlStateManager.enableCull() + } + + private fun colorForTime(delayedStart: Long): LorenzColor = when { + delayedStart < 1_000 -> LorenzColor.DARK_PURPLE + delayedStart < 3_000 -> LorenzColor.LIGHT_PURPLE + + else -> LorenzColor.WHITE + } + + @SubscribeEvent + fun onRenderLivingPost(event: RenderLivingEvent.Post<*>) { + if (!LorenzUtils.inDungeons) return + if (!LorenzMod.feature.dungeon.bossDamageIndicator) return + + try { + val entity = event.entity + + var ignoreBlocks = false + var delayedStart = -1L + val show = bossFinder?.shouldShow(entity, { ignoreBlocks = it }, { delayedStart = it }) ?: false + if (!show) return + + val currentMaxHealth = event.entity.baseMaxHealth + + val debugMaxHealth = getMaxHealthFor(event.entity) + val biggestHealth = max(currentMaxHealth, debugMaxHealth) + if (biggestHealth > debugMaxHealth) { + setMaxHealth(event.entity, biggestHealth) + } + + val percentage = event.entity.health / max(debugMaxHealth, currentMaxHealth) + val color = when { + percentage > 0.9 -> LorenzColor.DARK_GREEN + percentage > 0.75 -> LorenzColor.GREEN + percentage > 0.5 -> LorenzColor.YELLOW + percentage > 0.25 -> LorenzColor.GOLD + else -> LorenzColor.RED + } + + data[entity] = EntityData( + entity, + NumberUtil.format(event.entity.health), + color, + System.currentTimeMillis(), + ignoreBlocks, + delayedStart + ) + + } catch (e: Throwable) { + e.printStackTrace() + } + } + + //TODO test why this does not work + val maxHealth = mutableMapOf<UUID, Double>() + + private fun setMaxHealth(entity: EntityLivingBase, currentMaxHealth: Double) { + maxHealth[entity.uniqueID!!] = currentMaxHealth + } + + private fun getMaxHealthFor(entity: EntityLivingBase): Double { + return maxHealth.getOrDefault(entity.uniqueID!!, 0.0) + } + + @SubscribeEvent + fun onWorldRender(event: EntityJoinWorldEvent) { + bossFinder?.handleNewEntity(event.entity) + } +}
\ No newline at end of file diff --git a/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossFinder.kt b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossFinder.kt new file mode 100644 index 000000000..edc38edeb --- /dev/null +++ b/src/main/java/at/lorenz/mod/dungeon/damageindicator/DungeonBossFinder.kt @@ -0,0 +1,338 @@ +package at.lorenz.mod.dungeon.damageindicator + +import at.lorenz.mod.dungeon.DungeonData +import at.lorenz.mod.utils.LorenzUtils +import at.lorenz.mod.utils.LorenzUtils.baseMaxHealth +import at.lorenz.mod.utils.LorenzUtils.matchRegex +import at.lorenz.mod.utils.getLorenzVec +import net.minecraft.client.Minecraft +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.monster.EntityGiantZombie +import net.minecraft.entity.monster.EntityGuardian + +class DungeonBossFinder { + + //F1 + private var floor1bonzo1 = false + private var floor1bonzo1SpawnTime = 0L + private var floor1bonzo2 = false + private var floor1bonzo2SpawnTime = 0L + + //F2 + private var floor2summons1 = false + private var floor2summons1SpawnTime = 0L + private var floor2summonsDiedOnce = mutableListOf<EntityOtherPlayerMP>() + private var floor2secondPhase = false + private var floor2secondPhaseSpawnTime = 0L + + //F3 + private var floor3GuardianShield = false + private var floor3GuardianShieldSpawnTime = 0L + private var guardians = mutableListOf<EntityGuardian>() + private var floor3Professor = false + private var floor3ProfessorSpawnTime = 0L + private var floor3ProfessorGuardianPrepare = false + private var floor3ProfessorGuardianPrepareSpawnTime = 0L + private var floor3ProfessorGuardian = false + private var floor3ProfessorGuardianEntity: EntityGuardian? = null + + //F5 + private var floor5lividEntity: EntityOtherPlayerMP? = null + + //F6 + private var floor6Giants = false + private var floor6GiantsSpawnTime = 0L //TODO different giants cooldown delayed from each other + private var floor6Sadan = false + private var floor6SadanSpawnTime = 0L + + internal fun shouldShow( + entity: EntityLivingBase, + ignoreBlocks: (Boolean) -> Unit, + delayedStart: (Long) -> Unit + ): Boolean { + if (LorenzUtils.inDungeons) { + if (DungeonData.isOneOf("F1", "M1")) { + if (floor1bonzo1) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Bonzo ") { + delayedStart(floor1bonzo1SpawnTime) + return true + } + } + } + if (floor1bonzo2) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Bonzo ") { + delayedStart(floor1bonzo2SpawnTime) + return true + } + } + } + } + + if (DungeonData.isOneOf("F2", "M2")) { + if (entity.name == "Summon ") { + if (entity is EntityOtherPlayerMP) { + if (floor2summons1) { + if (!floor2summonsDiedOnce.contains(entity)) { + if (entity.health.toInt() != 0) { + delayedStart(floor2summons1SpawnTime) + return true + } else { + floor2summonsDiedOnce.add(entity) + } + } + } + if (floor2secondPhase) { + delayedStart(floor2secondPhaseSpawnTime) + return true + } + } + } + + if (floor2secondPhase) { + if (entity is EntityOtherPlayerMP) { + //TODO only show scarf after (all/at least x) summons are dead? + val result = entity.name == "Scarf " + if (result) { + delayedStart(floor2secondPhaseSpawnTime) + return true + } + } + } + } + + if (DungeonData.isOneOf("F3", "M3")) { + if (entity is EntityGuardian) { + if (floor3GuardianShield) { + if (guardians.size == 4) { + var totalHealth = 0 + for (guardian in guardians) { + totalHealth += guardian.health.toInt() + } + if (totalHealth == 0) { + floor3GuardianShield = false + guardians.clear() + } + } else { + findGuardians() + } + if (guardians.contains(entity)) { + ignoreBlocks(true) + delayedStart(floor3GuardianShieldSpawnTime) + return true + } + } + } + + if (floor3Professor) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "The Professor") { + delayedStart(floor3ProfessorSpawnTime) + ignoreBlocks(floor3ProfessorSpawnTime + 1_000 > System.currentTimeMillis()) + return true + } + } + } + if (floor3ProfessorGuardianPrepare) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "The Professor") { + delayedStart(floor3ProfessorGuardianPrepareSpawnTime) + ignoreBlocks(true) + return true + } + } + } + + if (entity is EntityGuardian) { + if (floor3ProfessorGuardian) { + if (entity == floor3ProfessorGuardianEntity) { + return true + } + } + } + } + + if (DungeonData.isOneOf("F5", "M5")) { + if (entity is EntityOtherPlayerMP) { + val floor5lividEntity1 = floor5lividEntity + if (entity == floor5lividEntity1) { + ignoreBlocks(entity.getLorenzVec().distance(5.5, 69.0, -2.5) < 5) + return true + } + } + } + + if (DungeonData.isOneOf("F6", "M6")) { + if (entity is EntityGiantZombie && !entity.isInvisible) { + if (floor6Giants && entity.posY > 68) { + ignoreBlocks(floor6GiantsSpawnTime + 1_000 > System.currentTimeMillis()) + delayedStart(floor6GiantsSpawnTime) + return true + } + + if (floor6Sadan) { + delayedStart(floor6SadanSpawnTime) + return true + } + } + } + } + + return false + } + + fun handleChat(message: String) { + when (message) { + //F1 + "§c[BOSS] Bonzo§r§f: Gratz for making it this far, but I’m basically unbeatable." -> { + floor1bonzo1 = true + floor1bonzo1SpawnTime = System.currentTimeMillis() + 11_250 + } + + "§c[BOSS] Bonzo§r§f: Oh noes, you got me.. what ever will I do?!" -> { + floor1bonzo1 = false + } + + "§c[BOSS] Bonzo§r§f: Oh I'm dead!" -> { + floor1bonzo2 = true + floor1bonzo2SpawnTime = System.currentTimeMillis() + 4_200 + } + + "§c[BOSS] Bonzo§r§f: Alright, maybe I'm just weak after all.." -> { + floor1bonzo2 = false + } + + //F2 + "§c[BOSS] Scarf§r§f: ARISE, MY CREATIONS!" -> { + floor2summons1 = true + floor2summons1SpawnTime = System.currentTimeMillis() + 3_500 + } + + "§c[BOSS] Scarf§r§f: Those toys are not strong enough I see." -> { + floor2summons1 = false + } + + "§c[BOSS] Scarf§r§f: Don't get too excited though." -> { + floor2secondPhase = true + floor2secondPhaseSpawnTime = System.currentTimeMillis() + 6_300 + } + + "§c[BOSS] Scarf§r§f: Whatever..." -> { + floor2secondPhase = false + } + + //F3 + "§c[BOSS] The Professor§r§f: I was burdened with terrible news recently..." -> { + floor3GuardianShield = true + floor3GuardianShieldSpawnTime = System.currentTimeMillis() + 16_400 + } + + "§c[BOSS] The Professor§r§f: Even if you took my barrier down, I can still fight." -> { + floor3GuardianShield = false + } + + "§c[BOSS] The Professor§r§f: Oh? You found my Guardians one weakness?" -> { + floor3Professor = true + floor3ProfessorSpawnTime = System.currentTimeMillis() + 10_300 + } + + "§c[BOSS] The Professor§r§f: I see. You have forced me to use my ultimate technique." -> { + floor3Professor = false + + floor3ProfessorGuardianPrepare = true + floor3ProfessorGuardianPrepareSpawnTime = System.currentTimeMillis() + 10_500 + } + + "§c[BOSS] The Professor§r§f: The process is irreversible, but I'll be stronger than a Wither now!" -> { + floor3ProfessorGuardian = true + } + + "§c[BOSS] The Professor§r§f: What?! My Guardian power is unbeatable!" -> { + floor3ProfessorGuardian = false + } + + + //F5 + "§c[BOSS] Livid§r§f: I respect you for making it to here, but I'll be your undoing." -> { + floor5lividEntity = findLivid() + } + + //F6 + "§c[BOSS] Sadan§r§f: ENOUGH!" -> { + floor6Giants = true + floor6GiantsSpawnTime = System.currentTimeMillis() + 7_400 + } + + "§c[BOSS] Sadan§r§f: You did it. I understand now, you have earned my respect." -> { + floor6Giants = false + floor6Sadan = true + floor6SadanSpawnTime = System.currentTimeMillis() + 32_500 + } + + "§c[BOSS] Sadan§r§f: NOOOOOOOOO!!! THIS IS IMPOSSIBLE!!" -> { + floor6Sadan = false + } + } + + if (message.matchRegex("§c\\[BOSS] (.*) Livid§r§f: Impossible! How did you figure out which one I was\\?!")) { + floor5lividEntity = null + } + } + + fun handleNewEntity(entity: Entity) { + if (floor3ProfessorGuardian) { + if (entity is EntityGuardian) { + if (floor3ProfessorGuardianEntity == null) { + floor3ProfessorGuardianEntity = entity + floor3ProfessorGuardianPrepare = false + } + } + } + } + + private fun findGuardians() { + guardians.clear() + + for (entity in Minecraft.getMinecraft().theWorld.loadedEntityList) { + if (entity is EntityGuardian) { + + val maxHealth = entity.baseMaxHealth.toInt() + + //F3 + if (maxHealth == 1_000_000 || maxHealth == 1_200_000) { + guardians.add(entity) + } + + //F3 Derpy + if (maxHealth == 2_000_000 || maxHealth == 2_400_000) { + guardians.add(entity) + } + + //M3 + if (maxHealth == 240_000_000 || maxHealth == 280_000_000) { + guardians.add(entity) + } + + //M3 Derpy + if (maxHealth == 120_000_000 || maxHealth == 140_000_000) { + guardians.add(entity) + } + } + } + } + + private fun findLivid(): EntityOtherPlayerMP? { + for (entity in Minecraft.getMinecraft().theWorld.loadedEntityList) { + if (entity is EntityOtherPlayerMP) { + if (entity.name == "Livid ") { + return entity + } + } + } + + return null + } +}
\ No newline at end of file diff --git a/src/main/java/at/lorenz/mod/dungeon/damageindicator/EntityData.kt b/src/main/java/at/lorenz/mod/dungeon/damageindicator/EntityData.kt new file mode 100644 index 000000000..4d60626fc --- /dev/null +++ b/src/main/java/at/lorenz/mod/dungeon/damageindicator/EntityData.kt @@ -0,0 +1,6 @@ +package at.lorenz.mod.dungeon.damageindicator + +import at.lorenz.mod.utils.LorenzColor +import net.minecraft.entity.EntityLivingBase + +class EntityData(val entity: EntityLivingBase, val text: String, val color: LorenzColor, val time: Long, val ignoreBlocks: Boolean, val delayedStart: Long)
\ No newline at end of file diff --git a/src/main/java/at/lorenz/mod/misc/HypixelData.kt b/src/main/java/at/lorenz/mod/misc/HypixelData.kt index 9d83e2679..e7c5a02f6 100644 --- a/src/main/java/at/lorenz/mod/misc/HypixelData.kt +++ b/src/main/java/at/lorenz/mod/misc/HypixelData.kt @@ -1,8 +1,6 @@ package at.lorenz.mod.misc -import at.lorenz.mod.events.DungeonEnterEvent import at.lorenz.mod.events.PacketEvent -import at.lorenz.mod.utils.LorenzUtils import net.minecraft.client.Minecraft import net.minecraft.network.play.server.S38PacketPlayerListItem import net.minecraft.network.play.server.S3DPacketDisplayScoreboard diff --git a/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt b/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt index 2295fa513..b36fbb94e 100644 --- a/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt +++ b/src/main/java/at/lorenz/mod/utils/LorenzUtils.kt @@ -2,6 +2,8 @@ package at.lorenz.mod.utils import at.lorenz.mod.misc.HypixelData import net.minecraft.client.Minecraft +import net.minecraft.entity.EntityLivingBase +import net.minecraft.entity.SharedMonsterAttributes import net.minecraft.util.ChatComponentText import org.intellij.lang.annotations.Language import java.text.SimpleDateFormat @@ -109,4 +111,7 @@ object LorenzUtils { } fun String.between(start: String, end: String): String = this.split(start, end)[1] + + val EntityLivingBase.baseMaxHealth: Double + get() = this.getEntityAttribute(SharedMonsterAttributes.maxHealth).baseValue }
\ No newline at end of file diff --git a/src/main/java/at/lorenz/mod/utils/RenderUtils.kt b/src/main/java/at/lorenz/mod/utils/RenderUtils.kt index 17532c613..7acd5b42b 100644 --- a/src/main/java/at/lorenz/mod/utils/RenderUtils.kt +++ b/src/main/java/at/lorenz/mod/utils/RenderUtils.kt @@ -9,6 +9,7 @@ import net.minecraft.inventory.Slot import net.minecraft.util.AxisAlignedBB import net.minecraft.util.MathHelper import net.minecraft.util.ResourceLocation +import net.minecraft.util.Vec3 import net.minecraftforge.client.event.RenderWorldLastEvent import org.lwjgl.opengl.GL11 import java.awt.Color @@ -309,4 +310,46 @@ object RenderUtils { GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) GlStateManager.popMatrix() } + + /** + * @author Mojang + */ + fun drawLabel( + pos: Vec3, + text: String, + color: Color, + partialTicks: Float, + shadow: Boolean = false, + scale: Float = 1f + ) { + var mc = Minecraft.getMinecraft() + val player = mc.thePlayer + val x = + pos.xCoord - player.lastTickPosX + (pos.xCoord - player.posX - (pos.xCoord - player.lastTickPosX)) * partialTicks + val y = + pos.yCoord - player.lastTickPosY + (pos.yCoord - player.posY - (pos.yCoord - player.lastTickPosY)) * partialTicks + val z = + pos.zCoord - player.lastTickPosZ + (pos.zCoord - player.posZ - (pos.zCoord - player.lastTickPosZ)) * partialTicks + val renderManager = mc.renderManager + val f1 = 0.0266666688 + val width = mc.fontRendererObj.getStringWidth(text) / 2 + GlStateManager.pushMatrix() + GlStateManager.translate(x, y, z) + GL11.glNormal3f(0f, 1f, 0f) + GlStateManager.rotate(-renderManager.playerViewY, 0f, 1f, 0f) + GlStateManager.rotate(renderManager.playerViewX, 1f, 0f, 0f) + GlStateManager.scale(-f1, -f1, -f1) + GlStateManager.scale(scale, scale, scale) + GlStateManager.enableBlend() + GlStateManager.disableLighting() + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0) + GlStateManager.enableTexture2D() + mc.fontRendererObj.drawString(text, (-width).toFloat(), 0f, color.rgb, shadow) + GlStateManager.disableBlend() + GlStateManager.popMatrix() + } + + fun interpolate(currentValue: Double, lastValue: Double, multiplier: Float): Double { + return lastValue + (currentValue - lastValue) * multiplier + } }
\ No newline at end of file |