|
| 1 | +package dev.betrix.superSmashMobsBrawl.abilities |
| 2 | + |
| 3 | +import dev.betrix.superSmashMobsBrawl.events.BrawlDamageEvent |
| 4 | +import dev.betrix.superSmashMobsBrawl.events.BrawlDamageType |
| 5 | +import dev.betrix.superSmashMobsBrawl.events.Damager |
| 6 | +import dev.betrix.superSmashMobsBrawl.extensions.isOnBlock |
| 7 | +import dev.betrix.superSmashMobsBrawl.extensions.setVelocity |
| 8 | +import gg.flyte.twilight.extension.getNearbyEntities |
| 9 | +import gg.flyte.twilight.scheduler.TwilightRunnable |
| 10 | +import gg.flyte.twilight.scheduler.repeatingTask |
| 11 | +import kotlin.math.abs |
| 12 | +import org.bukkit.Effect |
| 13 | +import org.bukkit.Sound |
| 14 | +import org.bukkit.block.BlockFace |
| 15 | +import org.bukkit.entity.LivingEntity |
| 16 | +import org.bukkit.entity.Player |
| 17 | +import org.bukkit.util.Vector |
| 18 | + |
| 19 | +class GlidingGallopAbility(player: Player) : BrawlAbility("gliding_gallop", player) { |
| 20 | + |
| 21 | + private val launchStrength: Double = metadata.double("launchStrength") ?: 1.0 |
| 22 | + private val launchYAdd: Double = metadata.double("launchYAdd") ?: 1.5 |
| 23 | + private val launchYMax: Double = metadata.double("launchYMax") ?: 2.0 |
| 24 | + private val landingCheckDelayTicks: Long = |
| 25 | + metadata.long("landingCheckDelayTicks") ?: 10L |
| 26 | + private val landingCheckIntervalTicks: Long = |
| 27 | + (metadata.long("landingCheckIntervalTicks") ?: 1L).coerceAtLeast(1L) |
| 28 | + private val impactDamage: Double = metadata.double("impactDamage") ?: 5.0 |
| 29 | + private val impactRadius: Double = metadata.double("impactRadius") ?: 4.0 |
| 30 | + private val impactKnockbackMultiplier: Double = |
| 31 | + metadata.double("impactKnockbackMultiplier") ?: 2.0 |
| 32 | + private val impactEnemyLaunchStrength: Double = |
| 33 | + metadata.double("impactEnemyLaunchStrength") ?: 1.8 |
| 34 | + private val impactEnemyLaunchYAdd: Double = |
| 35 | + metadata.double("impactEnemyLaunchYAdd") ?: 1.5 |
| 36 | + |
| 37 | + private var landingCheckTask: TwilightRunnable? = null |
| 38 | + |
| 39 | + override fun activate() { |
| 40 | + super.activate() |
| 41 | + |
| 42 | + // Launch player upward |
| 43 | + val launchDirection: Vector = |
| 44 | + player.location.direction.clone().apply { y = abs(y) } |
| 45 | + |
| 46 | + player.setVelocity( |
| 47 | + velocity = launchDirection, |
| 48 | + strength = launchStrength, |
| 49 | + ySet = false, |
| 50 | + yBase = 0.0, |
| 51 | + yAdd = launchYAdd, |
| 52 | + yMax = launchYMax, |
| 53 | + groundBoost = true, |
| 54 | + ) |
| 55 | + |
| 56 | + player.world.playSound(player.location, Sound.ENTITY_HORSE_JUMP, 1.5f, 1.0f) |
| 57 | + |
| 58 | + // Start checking for landing |
| 59 | + landingCheckTask?.cancel() |
| 60 | + landingCheckTask = |
| 61 | + repeatingTask(landingCheckDelayTicks, landingCheckIntervalTicks) { |
| 62 | + if (!player.isValid || player.isDead) { |
| 63 | + cancel() |
| 64 | + landingCheckTask = null |
| 65 | + return@repeatingTask |
| 66 | + } |
| 67 | + |
| 68 | + if (player.isOnBlock()) { |
| 69 | + cancel() |
| 70 | + landingCheckTask = null |
| 71 | + performImpact() |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + landingCheckTask?.let { runnables.add(it) } |
| 76 | + } |
| 77 | + |
| 78 | + override fun teardown() { |
| 79 | + landingCheckTask?.cancel() |
| 80 | + landingCheckTask = null |
| 81 | + super.teardown() |
| 82 | + } |
| 83 | + |
| 84 | + private fun performImpact() { |
| 85 | + val ownerLocation = player.location |
| 86 | + |
| 87 | + // Launch nearby enemies into the air |
| 88 | + ownerLocation |
| 89 | + .getNearbyEntities(impactRadius, impactRadius, impactRadius) |
| 90 | + .mapNotNull { it as? LivingEntity } |
| 91 | + .filter { it != player } |
| 92 | + .filter { canDamageEntity(it) } |
| 93 | + .forEach { target -> |
| 94 | + val distance = target.location.distance(ownerLocation) |
| 95 | + val normalizedDistance = |
| 96 | + ((impactRadius - distance) / impactRadius).coerceAtLeast(0.0) |
| 97 | + val scaledDamage = (impactDamage * normalizedDistance) + 0.5 |
| 98 | + |
| 99 | + BrawlDamageEvent( |
| 100 | + target, |
| 101 | + Damager.DamagerLivingEntity(player), |
| 102 | + scaledDamage, |
| 103 | + impactKnockbackMultiplier, |
| 104 | + BrawlDamageType.Explosion, |
| 105 | + ) |
| 106 | + .callEvent() |
| 107 | + |
| 108 | + // Launch enemy upward |
| 109 | + val launchDirection = |
| 110 | + target.location |
| 111 | + .toVector() |
| 112 | + .subtract(ownerLocation.toVector()) |
| 113 | + .normalize() |
| 114 | + target.setVelocity( |
| 115 | + velocity = launchDirection, |
| 116 | + strength = impactEnemyLaunchStrength * normalizedDistance, |
| 117 | + ySet = false, |
| 118 | + yBase = 0.0, |
| 119 | + yAdd = impactEnemyLaunchYAdd, |
| 120 | + yMax = 10.0, |
| 121 | + groundBoost = false, |
| 122 | + ) |
| 123 | + |
| 124 | + if (target is Player) { |
| 125 | + target.sendMessage( |
| 126 | + lang.t("messages.abilities.glidingGallop.hitBy") { |
| 127 | + "abilityId" to id |
| 128 | + "attacker" to player.name |
| 129 | + } |
| 130 | + ) |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + // Play impact sound and effects |
| 135 | + ownerLocation.world.playSound( |
| 136 | + ownerLocation, |
| 137 | + Sound.ENTITY_IRON_GOLEM_HURT, |
| 138 | + 2f, |
| 139 | + 0.6f, |
| 140 | + ) |
| 141 | + ownerLocation.world.playSound( |
| 142 | + ownerLocation, |
| 143 | + Sound.ENTITY_HORSE_LAND, |
| 144 | + 2f, |
| 145 | + 1.0f, |
| 146 | + ) |
| 147 | + |
| 148 | + // Spawn particle effects |
| 149 | + ownerLocation.world.spawnParticle( |
| 150 | + org.bukkit.Particle.EXPLOSION, |
| 151 | + ownerLocation, |
| 152 | + 3, |
| 153 | + 1.0, |
| 154 | + 0.5, |
| 155 | + 1.0, |
| 156 | + 0.0, |
| 157 | + ) |
| 158 | + |
| 159 | + // Trigger block effects |
| 160 | + val originBlock = ownerLocation.block |
| 161 | + for (x in -2..2) { |
| 162 | + for (z in -2..2) { |
| 163 | + val checkBlock = originBlock.getRelative(x, 0, z) |
| 164 | + |
| 165 | + if (!checkBlock.type.isSolid) { |
| 166 | + continue |
| 167 | + } |
| 168 | + |
| 169 | + val aboveBlock = checkBlock.getRelative(BlockFace.UP) |
| 170 | + |
| 171 | + if (aboveBlock.type.isSolid) { |
| 172 | + continue |
| 173 | + } |
| 174 | + |
| 175 | + if (Math.random() < 0.3) { |
| 176 | + ownerLocation.world.playEffect( |
| 177 | + checkBlock.location, |
| 178 | + Effect.STEP_SOUND, |
| 179 | + checkBlock.type, |
| 180 | + ) |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + private fun canDamageEntity(entity: LivingEntity): Boolean { |
| 187 | + if (entity !is Player) { |
| 188 | + return true |
| 189 | + } |
| 190 | + |
| 191 | + val minigame = minigameService.getMinigameForPlayer(player) ?: return true |
| 192 | + |
| 193 | + return !minigame.arePlayersOnSameTeam(player, entity) |
| 194 | + } |
| 195 | +} |
| 196 | + |
0 commit comments