Add Second Chance item
This commit is contained in:
parent
33a6a2dafd
commit
021ed57486
14 changed files with 214 additions and 21 deletions
|
@ -8,13 +8,15 @@ object ResourcePack {
|
||||||
val deathReason = Material.GRAY_STAINED_GLASS_PANE
|
val deathReason = Material.GRAY_STAINED_GLASS_PANE
|
||||||
val questionMark = Material.GRASS_BLOCK
|
val questionMark = Material.GRASS_BLOCK
|
||||||
val time = Material.CLOCK
|
val time = Material.CLOCK
|
||||||
|
val dot = Material.GRAY_STAINED_GLASS
|
||||||
|
val arrowDown = Material.WHITE_STAINED_GLASS
|
||||||
|
|
||||||
// Roles
|
// Roles
|
||||||
val innocent = Material.GREEN_STAINED_GLASS_PANE
|
val innocent = Material.GREEN_STAINED_GLASS_PANE
|
||||||
val detective = Material.YELLOW_STAINED_GLASS_PANE
|
val detective = Material.YELLOW_STAINED_GLASS_PANE
|
||||||
val traitor = Material.RED_STAINED_GLASS_PANE
|
val traitor = Material.RED_STAINED_GLASS_PANE
|
||||||
val jackal = Material.LIGHT_BLUE_STAINED_GLASS_PANE
|
val jackal = Material.LIGHT_BLUE_STAINED_GLASS_PANE
|
||||||
val sidekick = Material.LIGHT_BLUE_STAINED_GLASS_PANE
|
val sidekick = Material.BLUE_STAINED_GLASS_PANE
|
||||||
|
|
||||||
// Special Items
|
// Special Items
|
||||||
val cloakingDevice = Material.COBWEB
|
val cloakingDevice = Material.COBWEB
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.bukkit.event.EventHandler
|
||||||
import org.bukkit.event.Listener
|
import org.bukkit.event.Listener
|
||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||||
import org.bukkit.event.inventory.InventoryClickEvent
|
import org.bukkit.event.inventory.InventoryClickEvent
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent
|
||||||
import org.bukkit.event.player.PlayerEvent
|
import org.bukkit.event.player.PlayerEvent
|
||||||
import org.bukkit.event.player.PlayerInteractEvent
|
import org.bukkit.event.player.PlayerInteractEvent
|
||||||
import org.bukkit.event.player.PlayerItemConsumeEvent
|
import org.bukkit.event.player.PlayerItemConsumeEvent
|
||||||
|
@ -73,6 +74,13 @@ open class TTTItemListener(private val tttItem: TTTItem, private val cancelDamag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun handle(event: InventoryCloseEvent, handler: (tttPlayer: TTTPlayer) -> Unit) {
|
||||||
|
val player = event.player
|
||||||
|
if (player is Player) {
|
||||||
|
handler(PlayerManager.getTTTPlayer(player) ?: return)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected fun <T: PlayerEvent> handle(event: T, handler: (tttPlayer: TTTPlayer) -> Unit) {
|
protected fun <T: PlayerEvent> handle(event: T, handler: (tttPlayer: TTTPlayer) -> Unit) {
|
||||||
handler(PlayerManager.getTTTPlayer(event.player) ?: return)
|
handler(PlayerManager.getTTTPlayer(event.player) ?: return)
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,7 @@ class TTTCorpse private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
|
ensureNotDestroyed()
|
||||||
status = Status.DESTROYED
|
status = Status.DESTROYED
|
||||||
CorpseAPI.removeCorpse(corpse)
|
CorpseAPI.removeCorpse(corpse)
|
||||||
updateTimeListener.cancel()
|
updateTimeListener.cancel()
|
||||||
|
@ -146,15 +147,13 @@ class TTTCorpse private constructor(
|
||||||
private const val REASON_SLOT = 1
|
private const val REASON_SLOT = 1
|
||||||
private const val TIME_SLOT = 2
|
private const val TIME_SLOT = 2
|
||||||
|
|
||||||
fun spawn(tttPlayer: TTTPlayer, reason: DeathReason) {
|
fun spawn(tttPlayer: TTTPlayer, reason: DeathReason): TTTCorpse = TTTCorpse(
|
||||||
CorpseManager.add(TTTCorpse(
|
tttPlayer,
|
||||||
tttPlayer,
|
tttPlayer.player.location,
|
||||||
tttPlayer.player.location,
|
tttPlayer.role,
|
||||||
tttPlayer.role,
|
reason,
|
||||||
reason,
|
tttPlayer.credits
|
||||||
tttPlayer.credits
|
).also { CorpseManager.add(it) }
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun spawnFake(role: Role, tttPlayer: TTTPlayer, location: Location) {
|
fun spawnFake(role: Role, tttPlayer: TTTPlayer, location: Location) {
|
||||||
CorpseManager.add(TTTCorpse(
|
CorpseManager.add(TTTCorpse(
|
||||||
|
|
|
@ -39,7 +39,7 @@ class InversedStateContainer<T: IState>(private val stateClass: KClass<T>) {
|
||||||
|
|
||||||
val tttPlayers get() = PlayerManager.tttPlayers.filter { it.stateContainer.has(stateClass) }
|
val tttPlayers get() = PlayerManager.tttPlayers.filter { it.stateContainer.has(stateClass) }
|
||||||
|
|
||||||
fun forEachState(fn: (T, TTTPlayer) -> Unit) {
|
fun forEveryState(fn: (T, TTTPlayer) -> Unit) {
|
||||||
tttPlayers.forEach { it.stateContainer.get(stateClass)?.run { fn(this, it) } }
|
tttPlayers.forEach { it.stateContainer.get(stateClass)?.run { fn(this, it) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,15 +84,21 @@ class TTTPlayer(player: Player, role: Role) {
|
||||||
fun onDeath(reason: DeathReason = DeathReason.SUICIDE) {
|
fun onDeath(reason: DeathReason = DeathReason.SUICIDE) {
|
||||||
GameManager.ensurePhase(GamePhase.COMBAT)
|
GameManager.ensurePhase(GamePhase.COMBAT)
|
||||||
|
|
||||||
|
player.sendMessage(TTTPlugin.prefix + "${ChatColor.RED}${ChatColor.BOLD}Du bist gestorben")
|
||||||
|
|
||||||
player.gameMode = GameMode.SPECTATOR
|
player.gameMode = GameMode.SPECTATOR
|
||||||
alive = false
|
alive = false
|
||||||
TTTCorpse.spawn(this, reason)
|
val tttCorpse = TTTCorpse.spawn(this, reason)
|
||||||
|
|
||||||
player.inventory.clear()
|
player.inventory.clear()
|
||||||
credits = 0
|
credits = 0
|
||||||
|
|
||||||
// PlayerManager.letRemainingRoleGroupWin()
|
val event = TTTPlayerDeathEvent(this, player.location, tttCorpse)
|
||||||
plugin.server.pluginManager.callEvent(TTTPlayerDeathEvent(this, player.location))
|
plugin.server.pluginManager.callEvent(event)
|
||||||
|
|
||||||
|
if (event.letRoundEnd) {
|
||||||
|
// PlayerManager.letRemainingRoleGroupWin()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun revive(location: Location, credits: Int = 0) {
|
fun revive(location: Location, credits: Int = 0) {
|
||||||
|
@ -106,6 +112,7 @@ class TTTPlayer(player: Player, role: Role) {
|
||||||
|
|
||||||
Shop.setItems(this)
|
Shop.setItems(this)
|
||||||
|
|
||||||
|
plugin.server.pluginManager.callEvent(TTTPlayerReviveEvent(this))
|
||||||
player.sendMessage(TTTPlugin.prefix + "${ChatColor.GREEN}${ChatColor.BOLD}Du wurdest wiederbelebt")
|
player.sendMessage(TTTPlugin.prefix + "${ChatColor.GREEN}${ChatColor.BOLD}Du wurdest wiederbelebt")
|
||||||
|
|
||||||
plugin.server.scheduler.runTask(plugin, fun() {
|
plugin.server.scheduler.runTask(plugin, fun() {
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
package de.moritzruth.spigot_ttt.game.players
|
package de.moritzruth.spigot_ttt.game.players
|
||||||
|
|
||||||
|
import de.moritzruth.spigot_ttt.game.corpses.TTTCorpse
|
||||||
import org.bukkit.Location
|
import org.bukkit.Location
|
||||||
import org.bukkit.event.Event
|
import org.bukkit.event.Event
|
||||||
import org.bukkit.event.HandlerList
|
import org.bukkit.event.HandlerList
|
||||||
|
|
||||||
class TTTPlayerDeathEvent(
|
class TTTPlayerDeathEvent(
|
||||||
val tttPlayer: TTTPlayer,
|
val tttPlayer: TTTPlayer,
|
||||||
val location: Location
|
val location: Location,
|
||||||
|
val tttCorpse: TTTCorpse
|
||||||
): Event() {
|
): Event() {
|
||||||
override fun getHandlers(): HandlerList {
|
override fun getHandlers(): HandlerList {
|
||||||
@Suppress("RedundantCompanionReference") // false positive
|
@Suppress("RedundantCompanionReference") // false positive
|
||||||
return Companion.handlers
|
return Companion.handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var letRoundEnd = true
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val handlers = HandlerList()
|
private val handlers = HandlerList()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package de.moritzruth.spigot_ttt.game.players
|
||||||
|
|
||||||
|
import org.bukkit.event.Event
|
||||||
|
import org.bukkit.event.HandlerList
|
||||||
|
|
||||||
|
class TTTPlayerReviveEvent(val tttPlayer: TTTPlayer): Event() {
|
||||||
|
override fun getHandlers(): HandlerList {
|
||||||
|
@Suppress("RedundantCompanionReference") // false positive
|
||||||
|
return Companion.handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val handlers = HandlerList()
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getHandlerList() = handlers
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ object ItemManager {
|
||||||
BaseballBat,
|
BaseballBat,
|
||||||
CloakingDevice, Rifle,
|
CloakingDevice, Rifle,
|
||||||
EnderPearl, Radar, HealingPotion, Fireball,
|
EnderPearl, Radar, HealingPotion, Fireball,
|
||||||
Teleporter, MartyrdomGrenade, FakeCorpse, Defibrillator
|
Teleporter, MartyrdomGrenade, FakeCorpse, Defibrillator, SecondChance
|
||||||
)
|
)
|
||||||
|
|
||||||
val droppedItemStates = mutableMapOf<Int, IState>()
|
val droppedItemStates = mutableMapOf<Int, IState>()
|
||||||
|
|
|
@ -82,7 +82,7 @@ object Defibrillator: TTTItem, Buyable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, tttPlayer -> state.reset(tttPlayer) }
|
fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, tttPlayer -> state.reset(tttPlayer) }
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Action(val tttPlayer: TTTPlayer) {
|
sealed class Action(val tttPlayer: TTTPlayer) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ object MartyrdomGrenade: TTTItem, Buyable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, _ ->
|
fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, _ ->
|
||||||
state.explodeTask?.cancel()
|
state.explodeTask?.cancel()
|
||||||
state.explodeTask = null
|
state.explodeTask = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ object Radar: TTTItem, Buyable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, tttPlayer -> state.reset(tttPlayer) }
|
fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, tttPlayer -> state.reset(tttPlayer) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val packetListener = object : PacketAdapter(plugin, PacketType.Play.Server.ENTITY_METADATA) {
|
override val packetListener = object : PacketAdapter(plugin, PacketType.Play.Server.ENTITY_METADATA) {
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
package de.moritzruth.spigot_ttt.items.impl
|
||||||
|
|
||||||
|
import de.moritzruth.spigot_ttt.ResourcePack
|
||||||
|
import de.moritzruth.spigot_ttt.TTTItemListener
|
||||||
|
import de.moritzruth.spigot_ttt.game.GameEndEvent
|
||||||
|
import de.moritzruth.spigot_ttt.game.GameManager
|
||||||
|
import de.moritzruth.spigot_ttt.game.players.*
|
||||||
|
import de.moritzruth.spigot_ttt.items.Buyable
|
||||||
|
import de.moritzruth.spigot_ttt.items.PASSIVE
|
||||||
|
import de.moritzruth.spigot_ttt.items.TTTItem
|
||||||
|
import de.moritzruth.spigot_ttt.plugin
|
||||||
|
import de.moritzruth.spigot_ttt.utils.applyMeta
|
||||||
|
import de.moritzruth.spigot_ttt.utils.hideInfo
|
||||||
|
import de.moritzruth.spigot_ttt.utils.setAllToItem
|
||||||
|
import org.bukkit.ChatColor
|
||||||
|
import org.bukkit.Location
|
||||||
|
import org.bukkit.boss.BarColor
|
||||||
|
import org.bukkit.boss.BarStyle
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent
|
||||||
|
import org.bukkit.event.inventory.InventoryCloseEvent
|
||||||
|
import org.bukkit.event.inventory.InventoryType
|
||||||
|
import org.bukkit.inventory.ItemStack
|
||||||
|
import org.bukkit.scheduler.BukkitTask
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
object SecondChance: TTTItem, Buyable {
|
||||||
|
private val DISPLAY_NAME = "${ChatColor.GREEN}${ChatColor.BOLD}Second Chance"
|
||||||
|
val ON_CORPSE = ResourcePack.Items.arrowDown
|
||||||
|
val ON_SPAWN = ResourcePack.Items.dot
|
||||||
|
private const val TIMEOUT = 20.0
|
||||||
|
|
||||||
|
override val type = TTTItem.Type.SPECIAL
|
||||||
|
override val itemStack = ItemStack(ResourcePack.Items.secondChance).applyMeta {
|
||||||
|
setDisplayName("$DISPLAY_NAME $PASSIVE")
|
||||||
|
hideInfo()
|
||||||
|
lore = listOf(
|
||||||
|
"",
|
||||||
|
"${ChatColor.GOLD}Du wirst mit einer 50%-Chance",
|
||||||
|
"${ChatColor.GOLD}wiederbelebt, wenn du stirbst"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val buyableBy = roles(Role.TRAITOR, Role.JACKAL)
|
||||||
|
override val buyLimit = 1
|
||||||
|
override val price = 2
|
||||||
|
val isc = InversedStateContainer(State::class)
|
||||||
|
|
||||||
|
private val chooseSpawnInventory = plugin.server.createInventory(
|
||||||
|
null,
|
||||||
|
InventoryType.CHEST,
|
||||||
|
"${ChatColor.DARK_RED}${ChatColor.BOLD}Second Chance"
|
||||||
|
).apply {
|
||||||
|
setAllToItem(setOf(0, 1, 2, 9, 10, 11, 18, 19, 20), ItemStack(ON_CORPSE).applyMeta {
|
||||||
|
hideInfo()
|
||||||
|
setDisplayName("${ChatColor.GREEN}${ChatColor.BOLD}Bei der Leiche")
|
||||||
|
})
|
||||||
|
|
||||||
|
setAllToItem(setOf(3, 4, 5, 12, 13, 14, 21, 22, 23), ItemStack(ResourcePack.Items.textureless).applyMeta {
|
||||||
|
hideInfo()
|
||||||
|
setDisplayName("${ChatColor.RESET}${ChatColor.BOLD}Wo möchtest du spawnen?")
|
||||||
|
})
|
||||||
|
|
||||||
|
setAllToItem(setOf(6, 7, 8, 15, 16, 17, 24, 25, 26), ItemStack(ON_SPAWN).applyMeta {
|
||||||
|
hideInfo()
|
||||||
|
setDisplayName("${ChatColor.AQUA}${ChatColor.BOLD}Am Spawn")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBuy(tttPlayer: TTTPlayer) {
|
||||||
|
isc.getOrCreate(tttPlayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val listener = object : TTTItemListener(this, true) {
|
||||||
|
@EventHandler
|
||||||
|
fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) {
|
||||||
|
val state = isc.get(event.tttPlayer)
|
||||||
|
if (state != null) {
|
||||||
|
if (Random.nextBoolean()) {
|
||||||
|
event.letRoundEnd = false
|
||||||
|
event.tttPlayer.player.openInventory(chooseSpawnInventory)
|
||||||
|
state.timeoutAction = TimeoutAction(event.tttPlayer, event.tttCorpse.corpse.trueLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onInventoryClose(event: InventoryCloseEvent) = handle(event) { tttPlayer ->
|
||||||
|
if (event.inventory == chooseSpawnInventory) {
|
||||||
|
if (isc.get(tttPlayer)?.timeoutAction != null) {
|
||||||
|
plugin.server.scheduler.runTask(plugin, fun() {
|
||||||
|
if (isc.get(tttPlayer) != null) tttPlayer.player.openInventory(chooseSpawnInventory)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onInventoryClick(event: InventoryClickEvent) = handle(event) { tttPlayer ->
|
||||||
|
if (event.clickedInventory != chooseSpawnInventory) return@handle
|
||||||
|
val state = isc.get(tttPlayer) ?: return@handle
|
||||||
|
val timeoutAction = state.timeoutAction ?: return@handle
|
||||||
|
|
||||||
|
val location = when (event.currentItem?.type) {
|
||||||
|
ON_SPAWN -> GameManager.world.spawnLocation
|
||||||
|
ON_CORPSE -> timeoutAction.deathLocation
|
||||||
|
else -> return@handle
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutAction.stop()
|
||||||
|
tttPlayer.revive(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onTTTPlayerRevive(event: TTTPlayerReviveEvent) {
|
||||||
|
isc.get(event.tttPlayer)?.timeoutAction?.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onGameEnd(event: GameEndEvent) {
|
||||||
|
isc.forEveryState { state, _ -> state.timeoutAction?.stop() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeoutAction(
|
||||||
|
private val tttPlayer: TTTPlayer,
|
||||||
|
val deathLocation: Location
|
||||||
|
) {
|
||||||
|
private val startedAt = Instant.now()!!
|
||||||
|
private var bossBar = plugin.server.createBossBar(
|
||||||
|
"${ChatColor.GREEN}${ChatColor.BOLD}Second Chance",
|
||||||
|
BarColor.GREEN,
|
||||||
|
BarStyle.SOLID
|
||||||
|
).also { it.addPlayer(tttPlayer.player) }
|
||||||
|
|
||||||
|
private var task: BukkitTask = plugin.server.scheduler.runTaskTimer(plugin, fun() {
|
||||||
|
val duration = Duration.between(startedAt, Instant.now()).toMillis().toDouble() / 1000
|
||||||
|
val progress = duration / TIMEOUT
|
||||||
|
|
||||||
|
if (progress > 1) stop() else bossBar.progress = 1.0 - progress
|
||||||
|
}, 0, 1)
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
isc.remove(tttPlayer)
|
||||||
|
task.cancel()
|
||||||
|
tttPlayer.player.closeInventory()
|
||||||
|
bossBar.removePlayer(tttPlayer.player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class State: IState {
|
||||||
|
var timeoutAction: TimeoutAction? = null
|
||||||
|
}
|
||||||
|
}
|
|
@ -201,7 +201,7 @@ abstract class Gun(
|
||||||
fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) = isc.get(event.tttPlayer)?.reset()
|
fun onTTTPlayerDeath(event: TTTPlayerDeathEvent) = isc.get(event.tttPlayer)?.reset()
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
fun onGameEnd(event: GameEndEvent) = isc.forEachState { state, _ -> state.reset() }
|
fun onGameEnd(event: GameEndEvent) = isc.forEveryState { state, _ -> state.reset() }
|
||||||
}
|
}
|
||||||
|
|
||||||
class ActionInProgressError: RuntimeException("The gun has an ongoing action which may not be canceled")
|
class ActionInProgressError: RuntimeException("The gun has an ongoing action which may not be canceled")
|
||||||
|
|
|
@ -10,7 +10,7 @@ fun Inventory.setAllToItem(indexes: Iterable<Int>, itemStack: ItemStack) {
|
||||||
indexes.forEach { setItem(it, itemStack) }
|
indexes.forEach { setItem(it, itemStack) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Inventory.removeTTTItem(tttItem: TTTItem) = clear(indexOfFirst { it.type == tttItem.itemStack.type })
|
fun Inventory.removeTTTItem(tttItem: TTTItem) = clear(indexOfFirst { it?.type == tttItem.itemStack.type })
|
||||||
fun Inventory.removeTTTItemNextTick(tttItem: TTTItem) = plugin.server.scheduler.runTask(plugin, fun() {
|
fun Inventory.removeTTTItemNextTick(tttItem: TTTItem) = plugin.server.scheduler.runTask(plugin, fun() {
|
||||||
removeTTTItem(tttItem)
|
removeTTTItem(tttItem)
|
||||||
})
|
})
|
||||||
|
|
Reference in a new issue