diff --git a/src/main/resources/db/migration/V1.19.0__Create_break_trigger_preference_table.sql b/src/main/resources/db/migration/V1.19.0__Create_break_trigger_preference_table.sql new file mode 100644 index 0000000000..3ced2c8f23 --- /dev/null +++ b/src/main/resources/db/migration/V1.19.0__Create_break_trigger_preference_table.sql @@ -0,0 +1,7 @@ +USE seichiassist; + +CREATE TABLE player_break_suppression_preference( + uuid CHAR(36) NOT NULL, + do_break_suppression_due_to_mana BOOL NOT NULL DEFAULT FALSE, + FOREIGN KEY fk_player_break_suppression_preference_uuid REFERENCES playerdata(uuid) +); diff --git a/src/main/scala/com/github/unchama/seichiassist/SeichiAssist.scala b/src/main/scala/com/github/unchama/seichiassist/SeichiAssist.scala index f7359a6b8b..2d6d7ed128 100644 --- a/src/main/scala/com/github/unchama/seichiassist/SeichiAssist.scala +++ b/src/main/scala/com/github/unchama/seichiassist/SeichiAssist.scala @@ -63,6 +63,7 @@ import com.github.unchama.seichiassist.subsystems.autosave.application.SystemCon import com.github.unchama.seichiassist.subsystems.breakcount.{BreakCountAPI, BreakCountReadAPI} import com.github.unchama.seichiassist.subsystems.breakcountbar.BreakCountBarAPI import com.github.unchama.seichiassist.subsystems.breakskilltargetconfig.BreakSkillTargetConfigAPI +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.BreakSkillTriggerConfigAPI import com.github.unchama.seichiassist.subsystems.buildcount.BuildCountAPI import com.github.unchama.seichiassist.subsystems.discordnotification.DiscordNotificationAPI import com.github.unchama.seichiassist.subsystems.donate.DonatePremiumPointAPI @@ -495,6 +496,10 @@ class SeichiAssist extends JavaPlugin() { lazy val breakSkillTargetConfigSystem: subsystems.breakskilltargetconfig.System[IO, Player] = subsystems.breakskilltargetconfig.System.wired[IO, SyncIO].unsafeRunSync() + lazy val breakSkillTriggerConfigSystem + : subsystems.breakskilltriggerconfig.System[IO, Player] = + subsystems.breakskilltriggerconfig.System.wired[IO, SyncIO].unsafeRunSync() + /* TODO: mineStackSystemは本来privateであるべきだが、mineStackにアイテムを格納するAPIが現状の BreakUtilの実装から呼び出されている都合上やむを得ずpublicになっている。*/ lazy val mineStackSystem: subsystems.minestack.System[IO, Player, ItemStack] = @@ -562,6 +567,7 @@ class SeichiAssist extends JavaPlugin() { openirontrapdoor.System.wired, gridRegionSystem, breakSkillTargetConfigSystem, + breakSkillTriggerConfigSystem, joinAndQuitMessenger, elevatorSystem, blockLiquidStreamSystem, @@ -746,6 +752,8 @@ class SeichiAssist extends JavaPlugin() { implicit val gridRegionAPI: GridRegionAPI[IO, Player, Location] = gridRegionSystem.api implicit val breakSkillTargetConfigAPI: BreakSkillTargetConfigAPI[IO, Player] = breakSkillTargetConfigSystem.api + implicit val breakSkillTriggerConfigAPI: BreakSkillTriggerConfigAPI[IO, Player] = + breakSkillTriggerConfigSystem.api implicit val playerHeadSkinAPI: PlayerHeadSkinAPI[IO, Player] = playerHeadSkinSystem.api val menuRouter = TopLevelRouter.apply diff --git a/src/main/scala/com/github/unchama/seichiassist/listener/PlayerBlockBreakListener.scala b/src/main/scala/com/github/unchama/seichiassist/listener/PlayerBlockBreakListener.scala index ebc784fff2..bf09b7cf13 100644 --- a/src/main/scala/com/github/unchama/seichiassist/listener/PlayerBlockBreakListener.scala +++ b/src/main/scala/com/github/unchama/seichiassist/listener/PlayerBlockBreakListener.scala @@ -7,16 +7,19 @@ import com.github.unchama.minecraft.actions.OnMinecraftServerThread import com.github.unchama.seichiassist.ManagedWorld._ import com.github.unchama.seichiassist.MaterialSets.{BlockBreakableBySkill, BreakTool} import com.github.unchama.seichiassist.concurrent.PluginExecutionContexts +import com.github.unchama.seichiassist.data.{AxisAlignedCuboid} import com.github.unchama.seichiassist.seichiskill.ActiveSkillRange.MultiArea import com.github.unchama.seichiassist.seichiskill.SeichiSkillUsageMode.Disabled import com.github.unchama.seichiassist.seichiskill.{BlockSearching, BreakArea} import com.github.unchama.seichiassist.subsystems.breakcount.domain.level.SeichiExpAmount +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain.BreakSkillTriggerConfigKey import com.github.unchama.seichiassist.subsystems.mana.ManaApi import com.github.unchama.seichiassist.subsystems.mana.domain.ManaAmount import com.github.unchama.seichiassist.subsystems.minestack.MineStackAPI import com.github.unchama.seichiassist.util.BreakUtil import com.github.unchama.seichiassist.{MaterialSets, SeichiAssist} import com.github.unchama.targetedeffect.player.FocusedSoundEffect +import com.github.unchama.targetedeffect.player.ActionBarMessageEffect import com.github.unchama.util.bukkit.ItemStackUtil import com.github.unchama.util.effect.BukkitResources import com.github.unchama.util.external.WorldGuardWrapper @@ -101,20 +104,66 @@ class PlayerBlockBreakListener( return } - // 追加マナ獲得 - manaApi - .manaAmount(player) - .restoreAbsolute(ManaAmount(BreakUtil.calcManaDrop(player))) - .unsafeRunSync() - + // 選択したスキル val selectedSkill = skillState .activeSkill .getOrElse( return ) - if (!selectedSkill.range.isInstanceOf[MultiArea] || skillState.usageMode == Disabled) return + // 消費するマナが不足しているか判定 + { + // プレイヤーのY座標 + val playerLocY = player.getLocation.getBlockY - 1 + // スキル破壊範囲 + val skillArea = BreakArea(selectedSkill, skillState.usageMode) + // 破壊エリアリスト + val breakAreaList = skillArea.makeBreakArea(player).unsafeRunSync() + // 複数種類ブロック同時破壊設定 + val isMultiTypeBreakingSkillEnabled = + BreakUtil.performsMultipleIDBlockBreakWhenUsingSkills(player).unsafeRunSync() + // 破壊範囲のブロック計算 + val totalBreakRangeVolume = { + val breakLength = skillArea.breakLength + breakLength.x * breakLength.y * breakLength.z * skillArea.breakNum + } + breakAreaList.foreach { breakArea => + import com.github.unchama.seichiassist.data.syntax._ + val BlockSearching.Result(breakBlocks, waterBlocks, lavaBlocks) = + BlockSearching + .searchForBlocksBreakableWithSkill(player, breakArea.gridPoints(), block) + .unsafeRunSync() + .filterSolids(targetBlock => + isMultiTypeBreakingSkillEnabled || BlockSearching + .multiTypeBreakingFilterPredicate(block)(targetBlock) + ) + .filterAll(targetBlock => + player.isSneaking || targetBlock + .getLocation + .getBlockY > playerLocY || targetBlock == block + ) + + // 破壊範囲で消費されるマナ計算 + val manaToConsumeOnBreakArea = ManaAmount { + (gravity + 1) * selectedSkill.manaCost * (breakBlocks.size + 1).toDouble / totalBreakRangeVolume + } + // 消費マナが不足している場合は処理を終了 + manaApi.manaAmount(player).canAcquire(manaToConsumeOnBreakArea).unsafeRunSync() match { + case false if isBreakBlockManaFullyConsumed(player).unsafeRunSync() => + event.setCancelled(true) + return + case _ => + } + } + } + + // 追加マナ獲得 + manaApi + .manaAmount(player) + .restoreAbsolute(ManaAmount(BreakUtil.calcManaDrop(player))) + .unsafeRunSync() + // 破壊不可能ブロックの時処理を終了 if (!BreakUtil.canBreakWithSkill(player, block)) { event.setCancelled(true) @@ -123,21 +172,22 @@ class PlayerBlockBreakListener( event.setCancelled(true) + // ブロック破壊時に行う処理 { - // プレイヤーの足のy座標を取得 + // プレイヤーのY座標 val playerLocY = player.getLocation.getBlockY - 1 - + // スキル破壊範囲 val skillArea = BreakArea(selectedSkill, skillState.usageMode) + // 破壊エリアリスト val breakAreaList = skillArea.makeBreakArea(player).unsafeRunSync() - + // 複数種類ブロック同時破壊設定 val isMultiTypeBreakingSkillEnabled = BreakUtil.performsMultipleIDBlockBreakWhenUsingSkills(player).unsafeRunSync() - + // 破壊範囲のブロック計算 val totalBreakRangeVolume = { val breakLength = skillArea.breakLength breakLength.x * breakLength.y * breakLength.z * skillArea.breakNum } - // エフェクト用に壊されるブロック全てのリストデータ val multiBreakList = new ArrayBuffer[Set[BlockBreakableBySkill]] // 壊される溶岩の全てのリストデータ @@ -169,7 +219,6 @@ class PlayerBlockBreakListener( .getLocation .getBlockY > playerLocY || targetBlock == block ) - // このチャンクで消費されるマナ val manaToConsumeOnThisChunk = ManaAmount { (gravity + 1) * selectedSkill.manaCost * (breakBlocks.size + 1).toDouble / totalBreakRangeVolume @@ -373,4 +422,22 @@ class PlayerBlockBreakListener( event.setCancelled(true) player.sendMessage(s"${RED}Y-59以下に敷かれたハーフブロックは破壊不可能です。") } + + /** + * ブロック破壊時、「マナ切れブロック破壊停止設定」を取得する。 + * マナ切れブロック破壊設定が `true` になっている場合、プレイヤーに破壊抑制メッセージを送信する。 + * @param player マナ切れブロック破壊停止設定を取得するプレイヤー + */ + def isBreakBlockManaFullyConsumed(player: Player): IO[Boolean] = { + for { + isBreakBlockManaFullyConsumed <- SeichiAssist + .instance + .breakSkillTriggerConfigSystem + .api + .breakSkillTriggerConfig(player, BreakSkillTriggerConfigKey.ManaFullyConsumed) + _ <- ActionBarMessageEffect(s"${RED}マナ切れでブロック破壊を止めるスキルは有効化されています") + .run(player) + .whenA(isBreakBlockManaFullyConsumed) + } yield isBreakBlockManaFullyConsumed + } } diff --git a/src/main/scala/com/github/unchama/seichiassist/menus/TopLevelRouter.scala b/src/main/scala/com/github/unchama/seichiassist/menus/TopLevelRouter.scala index c7b44baa6c..7856db1575 100644 --- a/src/main/scala/com/github/unchama/seichiassist/menus/TopLevelRouter.scala +++ b/src/main/scala/com/github/unchama/seichiassist/menus/TopLevelRouter.scala @@ -31,6 +31,7 @@ import com.github.unchama.seichiassist.subsystems.breakcount.BreakCountAPI import com.github.unchama.seichiassist.subsystems.breakcount.domain.SeichiAmountData import com.github.unchama.seichiassist.subsystems.breakcountbar.BreakCountBarAPI import com.github.unchama.seichiassist.subsystems.breakskilltargetconfig.BreakSkillTargetConfigAPI +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.BreakSkillTriggerConfigAPI import com.github.unchama.seichiassist.subsystems.buildcount.domain.playerdata.BuildAmountData import com.github.unchama.seichiassist.subsystems.discordnotification.DiscordNotificationAPI import com.github.unchama.seichiassist.subsystems.donate.DonatePremiumPointAPI @@ -104,6 +105,7 @@ object TopLevelRouter { fairySpeechAPI: FairySpeechAPI[IO, Player], gridRegionAPI: GridRegionAPI[IO, Player, Location], breakSkillTargetConfigAPI: BreakSkillTargetConfigAPI[IO, Player], + breakSkillTriggerConfigAPI: BreakSkillTriggerConfigAPI[IO, Player], playerHeadSkinAPI: PlayerHeadSkinAPI[IO, Player] ): TopLevelRouter[IO] = new TopLevelRouter[IO] { import assortedRankingApi._ diff --git a/src/main/scala/com/github/unchama/seichiassist/menus/skill/PassiveSkillMenu.scala b/src/main/scala/com/github/unchama/seichiassist/menus/skill/PassiveSkillMenu.scala index 9def38cd0f..2bdb75b292 100644 --- a/src/main/scala/com/github/unchama/seichiassist/menus/skill/PassiveSkillMenu.scala +++ b/src/main/scala/com/github/unchama/seichiassist/menus/skill/PassiveSkillMenu.scala @@ -13,6 +13,8 @@ import com.github.unchama.seichiassist.menus.stickmenu.FirstPage import com.github.unchama.seichiassist.subsystems.breakcount.BreakCountAPI import com.github.unchama.seichiassist.subsystems.breakskilltargetconfig.BreakSkillTargetConfigAPI import com.github.unchama.seichiassist.subsystems.breakskilltargetconfig.domain.BreakSkillTargetConfigKey +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.BreakSkillTriggerConfigAPI +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain.BreakSkillTriggerConfigKey import com.github.unchama.seichiassist.subsystems.playerheadskin.PlayerHeadSkinAPI import com.github.unchama.targetedeffect._ import com.github.unchama.targetedeffect.commandsender.MessageEffect @@ -34,6 +36,7 @@ object PassiveSkillMenu extends Menu { class Environment( implicit val breakCountApi: BreakCountAPI[IO, SyncIO, Player], implicit val breakSkillTargetConfigAPI: BreakSkillTargetConfigAPI[IO, Player], + implicit val breakSkillTriggerConfigAPI: BreakSkillTriggerConfigAPI[IO, Player], val ioCanOpenFirstPage: IO CanOpen FirstPage.type, implicit val playerHeadSkinAPI: PlayerHeadSkinAPI[IO, Player] ) @@ -62,6 +65,7 @@ object PassiveSkillMenu extends Menu { val dynamicPartComputation = List( ChestSlotRef(0, 0) -> computeToggleMultipleBlockTypeDestructionButton, ChestSlotRef(0, 1) -> computeToggleChestBreakButton, + ChestSlotRef(0, 2) -> computeToggleManaFullyConsumedBreakStopButton, ChestSlotRef(1, 0) -> computeGiganticBerserkButton, ChestSlotRef(1, 1) -> computeToggleNetherQuartzBlockButton ).traverse(_.sequence) @@ -231,6 +235,49 @@ object PassiveSkillMenu extends Menu { ) }) + val computeToggleManaFullyConsumedBreakStopButton: IO[Button] = RecomputedButton(for { + originalBreakStopConfig <- breakSkillTriggerConfigAPI + .breakSkillTriggerConfig(player, BreakSkillTriggerConfigKey.ManaFullyConsumed) + } yield { + val baseLore = List(s"${YELLOW}マナ切れでブロック破壊を止めるスキル") + val statusLore = if (originalBreakStopConfig) { + List(s"${GREEN}ON (マナが切れるとブロック破壊を止めます。)", s"${DARK_RED}クリックでOFF") + } else { + List(s"${RED}OFF (マナが切れてもブロック破壊を続けます。)", s"${DARK_GREEN}クリックでON") + } + + Button( + new IconItemStackBuilder(Material.LAPIS_LAZULI) + .tap { builder => + if (originalBreakStopConfig) + builder.enchanted() + } + .title(s"$WHITE$UNDERLINE${BOLD}マナ切れでブロック破壊を止めるスキル切り替え") + .lore(baseLore ++ statusLore) + .build(), + LeftClickButtonEffect { + SequentialEffect( + breakSkillTriggerConfigAPI.toggleBreakSkillTriggerConfig( + BreakSkillTriggerConfigKey.ManaFullyConsumed + ), + DeferredEffect(IO { + if (!originalBreakStopConfig) { + SequentialEffect( + MessageEffect(s"${GREEN}マナが切れたらブロック破壊を止めるスキルを有効化しました。"), + FocusedSoundEffect(Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1f, 1f) + ) + } else { + SequentialEffect( + MessageEffect(s"${RED}マナが切れたらブロック破壊を止めるスキルを無効化しました。"), + FocusedSoundEffect(Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1f, 0.5f) + ) + } + }) + ) + } + ) + }) + val computeGiganticBerserkButton: IO[Button] = RecomputedButton { environment .breakCountApi diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/BreakSkillTriggerConfigAPI.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/BreakSkillTriggerConfigAPI.scala new file mode 100644 index 0000000000..1df97cf935 --- /dev/null +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/BreakSkillTriggerConfigAPI.scala @@ -0,0 +1,28 @@ +package com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig + +import cats.data.Kleisli +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain.BreakSkillTriggerConfigKey + +trait BreakSkillTriggerConfigAPI[F[_], Player] { + + /** + * @return 破壊トリガをトグルする作用 + */ + def toggleBreakSkillTriggerConfig( + configKey: BreakSkillTriggerConfigKey + ): Kleisli[F, Player, Unit] + + /** + * @return 現在の破壊トリガを取得する作用 + */ + def breakSkillTriggerConfig(player: Player, configKey: BreakSkillTriggerConfigKey): F[Boolean] + +} + +object BreakSkillTriggerConfigAPI { + + def apply[F[_], Player]( + implicit ev: BreakSkillTriggerConfigAPI[F, Player] + ): BreakSkillTriggerConfigAPI[F, Player] = ev + +} diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/System.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/System.scala new file mode 100644 index 0000000000..4a86270d70 --- /dev/null +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/System.scala @@ -0,0 +1,67 @@ +package com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig + +import cats.data.Kleisli +import cats.effect.SyncEffect +import com.github.unchama.datarepository.bukkit.player.BukkitRepositoryControls +import com.github.unchama.generic.ContextCoercion +import com.github.unchama.seichiassist.meta.subsystem.Subsystem +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.application.repository.BreakSkillTriggerConfigRepositoryDefinition +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain.{ + BreakSkillTriggerConfigKey, + BreakSkillTriggerConfigPersistence +} +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.persistence.JdbcBreakSkillTriggerConfigPersistence +import org.bukkit.entity.Player + +trait System[F[_], Player] extends Subsystem[F] { + val api: BreakSkillTriggerConfigAPI[F, Player] +} + +object System { + + import cats.implicits._ + + def wired[F[_], G[_]: SyncEffect: ContextCoercion[*[_], F]]: G[System[F, Player]] = { + implicit val breakSkillTriggerConfigPersistence: BreakSkillTriggerConfigPersistence[G] = + new JdbcBreakSkillTriggerConfigPersistence[G] + + for { + breakSkillTriggerConfigRepositoryControls <- BukkitRepositoryControls.createHandles( + BreakSkillTriggerConfigRepositoryDefinition.withContext[G, Player] + ) + } yield { + val breakSkillTriggerConfigRepository = + breakSkillTriggerConfigRepositoryControls.repository + + new System[F, Player] { + override val api: BreakSkillTriggerConfigAPI[F, Player] = + new BreakSkillTriggerConfigAPI[F, Player] { + override def toggleBreakSkillTriggerConfig( + configKey: BreakSkillTriggerConfigKey + ): Kleisli[F, Player, Unit] = + Kleisli { player => + ContextCoercion( + breakSkillTriggerConfigRepository(player) + .update(_.toggleBreakSkillTriggerConfig(configKey)) + ) + } + + override def breakSkillTriggerConfig( + player: Player, + configKey: BreakSkillTriggerConfigKey + ): F[Boolean] = + ContextCoercion( + breakSkillTriggerConfigRepository(player) + .get + .map(_.breakSkillTriggerConfig(configKey)) + ) + } + + override val managedRepositoryControls: Seq[BukkitRepositoryControls[F, _]] = Seq( + breakSkillTriggerConfigRepositoryControls.coerceFinalizationContextTo[F] + ) + } + } + } + +} diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/application/BreakSkillTriggerConfigRepositoryDefinition.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/application/BreakSkillTriggerConfigRepositoryDefinition.scala new file mode 100644 index 0000000000..89339ce26d --- /dev/null +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/application/BreakSkillTriggerConfigRepositoryDefinition.scala @@ -0,0 +1,23 @@ +package com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.application.repository + +import cats.effect.Sync +import cats.effect.concurrent.Ref +import com.github.unchama.datarepository.definitions.RefDictBackedRepositoryDefinition +import com.github.unchama.datarepository.template.RepositoryDefinition +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain.{ + BreakSkillTriggerConfig, + BreakSkillTriggerConfigPersistence +} + +object BreakSkillTriggerConfigRepositoryDefinition { + + def withContext[F[_]: Sync, Player]( + implicit persistence: BreakSkillTriggerConfigPersistence[F] + ): RepositoryDefinition[F, Player, Ref[F, BreakSkillTriggerConfig]] = + RefDictBackedRepositoryDefinition + .usingUuidRefDict[F, Player, BreakSkillTriggerConfig](persistence)( + BreakSkillTriggerConfig.initial + ) + .toRefRepository + +} diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfig.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfig.scala new file mode 100644 index 0000000000..8111bc8286 --- /dev/null +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfig.scala @@ -0,0 +1,28 @@ +package com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain + +case class BreakSkillTriggerConfig(config: Map[BreakSkillTriggerConfigKey, Boolean]) { + + /** + * @return `configKey`の破壊トリガをトグルする + */ + def toggleBreakSkillTriggerConfig( + configKey: BreakSkillTriggerConfigKey + ): BreakSkillTriggerConfig = + this.copy(this.config + (configKey -> !this.config.getOrElse(configKey, false))) + + /** + * @return 現在の破壊トリガを取得する + */ + def breakSkillTriggerConfig(configKey: BreakSkillTriggerConfigKey): Boolean = + this.config.getOrElse(configKey, false) + +} + +object BreakSkillTriggerConfig { + + /** + * [[BreakSkillTriggerConfig]]の初期値 + */ + val initial: BreakSkillTriggerConfig = BreakSkillTriggerConfig(Map.empty) + +} diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfigKey.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfigKey.scala new file mode 100644 index 0000000000..293979b2d0 --- /dev/null +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfigKey.scala @@ -0,0 +1,21 @@ +package com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain + +import enumeratum.{Enum, EnumEntry} + +/** + * トリガ条件の集合を表現する。 + * これらの値は整地スキルでブロックを破壊する際に、 + * プレイヤーの設定に応じて使われる。 + */ +sealed trait BreakSkillTriggerConfigKey extends EnumEntry + +object BreakSkillTriggerConfigKey extends Enum[BreakSkillTriggerConfigKey] { + + val values: IndexedSeq[BreakSkillTriggerConfigKey] = findValues + + /** + * マナを消費しきった場合にブロックを破壊するかどうか + */ + case object ManaFullyConsumed extends BreakSkillTriggerConfigKey + +} diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfigPersistence.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfigPersistence.scala new file mode 100644 index 0000000000..938ba6b70f --- /dev/null +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/domain/BreakSkillTriggerConfigPersistence.scala @@ -0,0 +1,7 @@ +package com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain + +import com.github.unchama.generic.RefDict + +import java.util.UUID + +trait BreakSkillTriggerConfigPersistence[F[_]] extends RefDict[F, UUID, BreakSkillTriggerConfig] diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/infrastructure/JdbcBreakSkillTriggerConfigPersistence.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/infrastructure/JdbcBreakSkillTriggerConfigPersistence.scala new file mode 100644 index 0000000000..1b656ca440 --- /dev/null +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/breakskilltriggerconfig/infrastructure/JdbcBreakSkillTriggerConfigPersistence.scala @@ -0,0 +1,52 @@ +package com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.persistence + +import cats.effect.Sync +import com.github.unchama.seichiassist.subsystems.breakskilltriggerconfig.domain.{ + BreakSkillTriggerConfig, + BreakSkillTriggerConfigKey, + BreakSkillTriggerConfigPersistence +} +import scalikejdbc.{DB, scalikejdbcSQLInterpolationImplicitDef} + +import java.util.UUID + +class JdbcBreakSkillTriggerConfigPersistence[F[_]: Sync] + extends BreakSkillTriggerConfigPersistence[F] { + + override def read(key: UUID): F[Option[BreakSkillTriggerConfig]] = Sync[F].delay { + val config = DB.readOnly { implicit session => + sql"SELECT trigger_category, do_not_break FROM player_break_trigger_preference WHERE uuid = ${key.toString}" + .map { rs => + BreakSkillTriggerConfigKey.withNameOption(rs.string("trigger_category")).map { + flagName => flagName -> rs.boolean("do_not_break") + } + } + .toList() + .flatten + .toMap + } + + Some(BreakSkillTriggerConfig(config)) + } + + override def write(key: UUID, value: BreakSkillTriggerConfig): F[Unit] = Sync[F].delay { + DB.localTx { implicit session => + val uuid = key.toString + val batchParams = + value + .config + .map { + case (triggerCategory, doNotBreak) => + Seq(uuid, triggerCategory.entryName, doNotBreak) + } + .toSeq + + sql"""INSERT INTO player_break_trigger_preference (uuid, trigger_category, do_not_break) + | VALUES (?, ?, ?) + | ON DUPLICATE KEY UPDATE + | do_not_break = VALUE(do_not_break) + """.stripMargin.batch(batchParams: _*).apply[List]() + } + } + +} diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/LevelCappedManaAmount.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/LevelCappedManaAmount.scala index 7c75a89e4e..5abd116b5a 100644 --- a/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/LevelCappedManaAmount.scala +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/LevelCappedManaAmount.scala @@ -22,6 +22,12 @@ case class LevelCappedManaAmount private (manaAmount: ManaAmount, level: SeichiL manaAmount.tryUse(amount)(manaMultiplier).map(LevelCappedManaAmount(_, level)) } + def tryConsume( + amount: ManaAmount + )(manaMultiplier: ManaMultiplier): Option[LevelCappedManaAmount] = { + manaAmount.tryConsume(amount)(manaMultiplier).map(LevelCappedManaAmount(_, level)) + } + def withHigherLevelOption(newLevel: SeichiLevel): Option[LevelCappedManaAmount] = Option.when(newLevel > level)(LevelCappedManaAmount(manaAmount, newLevel).fillToCap) diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaAmount.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaAmount.scala index 4744ed8501..d86abdd3f6 100644 --- a/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaAmount.scala +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaAmount.scala @@ -12,6 +12,13 @@ case class ManaAmount(value: Double) { Option.when(resultingAmount >= 0.0)(ManaAmount(resultingAmount)) } + def tryConsume(amount: ManaAmount)(manaMultiplier: ManaMultiplier): Option[ManaAmount] = { + val resultingAmount = value + Option.when(resultingAmount >= amount.multiply(manaMultiplier.value).value)( + ManaAmount(resultingAmount) + ) + } + def multiply(rate: Double): ManaAmount = { require(rate >= 0.0, "マナ量乗算の倍率は非負である必要があります") ManaAmount(rate * value) diff --git a/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaManipulation.scala b/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaManipulation.scala index ded6a9a728..020a0efc81 100644 --- a/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaManipulation.scala +++ b/src/main/scala/com/github/unchama/seichiassist/subsystems/mana/domain/ManaManipulation.scala @@ -28,6 +28,11 @@ trait ManaManipulation[F[_]] { */ def tryAcquire(amount: ManaAmount): F[Option[ManaAmount]] + /** + * `amount` だけマナを消費することが可能ならば `true`、そうでないなら `false` を返す作用 + */ + def canAcquire(amount: ManaAmount): F[Boolean] + } object ManaManipulation { @@ -54,6 +59,16 @@ object ManaManipulation { } } } + override def canAcquire(amount: ManaAmount): F[Boolean] = { + dragonNightTimeMultiplierRef.get.flatMap { multiplier => + ref.modify { original => + original.tryConsume(amount)(multiplier) match { + case Some(reduced) => (reduced, true) + case None => (original, false) + } + } + } + } } }