mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2026-08-23 03:15:10 -06:00
ContentAPI
This commit is contained in:
parent
624ee69b06
commit
a741ab7f06
9 changed files with 92 additions and 99 deletions
|
|
@ -20,7 +20,8 @@ class SandSourceListener : InteractionListener {
|
|||
private val SAND_PITS = intArrayOf(
|
||||
Scenery.SAND_PIT_2645,
|
||||
Scenery.SAND_PIT_4373,
|
||||
Scenery.SANDPIT_10814
|
||||
Scenery.SANDPIT_10814,
|
||||
Scenery.SANDPIT_22726
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,22 +4,25 @@ import core.api.MapArea
|
|||
import core.api.closeInterface
|
||||
import core.api.closeOverlay
|
||||
import core.api.closeTabInterface
|
||||
import core.api.delayScript
|
||||
import core.api.face
|
||||
import core.api.getAttribute
|
||||
import core.api.getRegionBorders
|
||||
import core.api.openOverlay
|
||||
import core.api.playAudio
|
||||
import core.api.queueScript
|
||||
import core.api.removeAttribute
|
||||
import core.api.sendMessage
|
||||
import core.api.sendPlayerDialogue
|
||||
import core.api.setAttribute
|
||||
import core.api.submitIndividualPulse
|
||||
import core.api.stopExecuting
|
||||
import core.api.teleport
|
||||
import core.api.unlock
|
||||
import core.game.dialogue.DialogueLabeller
|
||||
import core.game.interaction.IntType
|
||||
import core.game.interaction.InteractionListener
|
||||
import core.game.interaction.QueueStrength
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.system.task.Pulse
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.zone.ZoneBorders
|
||||
import core.game.world.map.zone.ZoneRestriction
|
||||
|
|
@ -80,7 +83,7 @@ object BalloonCrashSites {
|
|||
closeFlightInterface(player)
|
||||
teleport(player, OCEAN_CRASH_LANDING)
|
||||
oceanSwimmingAnimations(player)
|
||||
player.faceLocation(OCEAN_CRASH_FACE_LOCATION)
|
||||
face(player, OCEAN_CRASH_FACE_LOCATION)
|
||||
sendMessage(player, "The balloon crashes.")
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +94,7 @@ object BalloonCrashSites {
|
|||
player.appearance.setAnimations()
|
||||
player.appearance.sync()
|
||||
teleport(player, OCEAN_CRASH_EXIT)
|
||||
player.faceLocation(OCEAN_CRASH_EXIT_FACE_LOCATION)
|
||||
face(player, OCEAN_CRASH_EXIT_FACE_LOCATION)
|
||||
sendMessage(player, "You manage to swim back to shore.")
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +105,7 @@ object BalloonCrashSites {
|
|||
fadeTransition(player) {
|
||||
closeFlightInterface(player)
|
||||
teleport(player, WOODLAND_CRASH_LANDING)
|
||||
player.faceLocation(WOODLAND_CRASH_FACE_LOCATION)
|
||||
face(player, WOODLAND_CRASH_FACE_LOCATION)
|
||||
sendMessage(player, "The balloon crashes.")
|
||||
}
|
||||
}
|
||||
|
|
@ -114,13 +117,13 @@ object BalloonCrashSites {
|
|||
when (exit) {
|
||||
WoodlandExit.FALADOR -> {
|
||||
teleport(player, WOODLAND_FALADOR_EXIT)
|
||||
player.faceLocation(WOODLAND_FALADOR_EXIT_FACE_LOCATION)
|
||||
face(player, WOODLAND_FALADOR_EXIT_FACE_LOCATION)
|
||||
sendPlayerDialogue(player, "The crash must have really sent us off course; I'm near Falador!")
|
||||
}
|
||||
|
||||
WoodlandExit.ARDOUGNE -> {
|
||||
teleport(player, WOODLAND_ARDOUGNE_EXIT)
|
||||
player.faceLocation(WOODLAND_ARDOUGNE_EXIT_FACE_LOCATION)
|
||||
face(player, WOODLAND_ARDOUGNE_EXIT_FACE_LOCATION)
|
||||
// TODO: Placeholder message, find 09 source.
|
||||
sendPlayerDialogue(player, "The crash must have really sent us off course; I'm near Ardougne!")
|
||||
}
|
||||
|
|
@ -147,44 +150,32 @@ object BalloonCrashSites {
|
|||
if (fadeToBlack) {
|
||||
player.lock()
|
||||
openOverlay(player, Components.FADE_TO_BLACK_120)
|
||||
submitIndividualPulse(player, object : Pulse() {
|
||||
var counter = 0
|
||||
override fun pulse(): Boolean {
|
||||
when (counter++) {
|
||||
4 -> {
|
||||
whileBlack()
|
||||
closeOverlay(player)
|
||||
openOverlay(player, Components.FADE_FROM_BLACK_170)
|
||||
}
|
||||
|
||||
7 -> {
|
||||
closeOverlay(player)
|
||||
unlock(player)
|
||||
return true
|
||||
}
|
||||
queueScript(player, 4, QueueStrength.SOFT) { stage ->
|
||||
when (stage) {
|
||||
0 -> {
|
||||
whileBlack()
|
||||
closeOverlay(player)
|
||||
openOverlay(player, Components.FADE_FROM_BLACK_170)
|
||||
return@queueScript delayScript(player, 3)
|
||||
}
|
||||
|
||||
else -> {
|
||||
closeOverlay(player)
|
||||
unlock(player)
|
||||
return@queueScript stopExecuting(player)
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
whileBlack()
|
||||
closeOverlay(player)
|
||||
openOverlay(player, Components.FADE_FROM_BLACK_170)
|
||||
submitIndividualPulse(player, object : Pulse() {
|
||||
var counter = 0
|
||||
|
||||
override fun pulse(): Boolean {
|
||||
when (counter++) {
|
||||
3 -> {
|
||||
closeOverlay(player)
|
||||
unlock(player)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
queueScript(player, 3, QueueStrength.SOFT) {
|
||||
closeOverlay(player)
|
||||
unlock(player)
|
||||
return@queueScript stopExecuting(player)
|
||||
}
|
||||
}
|
||||
|
||||
private fun oceanSwimmingAnimations(player: Player) {
|
||||
|
|
|
|||
|
|
@ -5,22 +5,24 @@ import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJou
|
|||
import core.api.closeInterface
|
||||
import core.api.closeOverlay
|
||||
import core.api.closeTabInterface
|
||||
import core.api.face
|
||||
import core.api.getAttribute
|
||||
import core.api.openInterface
|
||||
import core.api.openOverlay
|
||||
import core.api.openSingleTab
|
||||
import core.api.playAudio
|
||||
import core.api.removeAttribute
|
||||
import core.api.queueScript
|
||||
import core.api.removeAttributes
|
||||
import core.api.sendMessage
|
||||
import core.api.setAttribute
|
||||
import core.api.setQuestStage
|
||||
import core.api.setVarbit
|
||||
import core.api.submitIndividualPulse
|
||||
import core.api.stopExecuting
|
||||
import core.api.teleport
|
||||
import core.api.unlock
|
||||
import core.game.interaction.InterfaceListener
|
||||
import core.game.interaction.QueueStrength
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.system.task.Pulse
|
||||
import core.game.world.map.Location
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.MinimapStateContext
|
||||
|
|
@ -289,7 +291,7 @@ class BalloonFlightInterface : InterfaceListener {
|
|||
showGameTabs(player)
|
||||
showMinimap(player)
|
||||
teleport(player, BalloonFlightRoutes.TAVERLEY_LANDING)
|
||||
player.faceLocation(Location.create(2941, 3421, 0))
|
||||
face(player, Location.create(2941, 3421, 0))
|
||||
sendMessage(player, "You arrive safely in Taverley.")
|
||||
setVarbit(player, EnlightenedJourney.VARBIT_TAVERLEY_BALLOON, 1, true)
|
||||
setQuestStage(player, Quests.ENLIGHTENED_JOURNEY, EnlightenedJourney.STAGE_CLAIM_REWARD)
|
||||
|
|
@ -450,11 +452,13 @@ class BalloonFlightInterface : InterfaceListener {
|
|||
}
|
||||
|
||||
private fun clearFlightAttributes(player: Player) {
|
||||
removeAttribute(player, ATTRIBUTE_BALLOON_TOP)
|
||||
removeAttribute(player, ATTRIBUTE_BALLOON_BOTTOM)
|
||||
removeAttribute(player, ATTRIBUTE_FLIGHT_DESTINATION)
|
||||
removeAttribute(player, ATTRIBUTE_FLIGHT_SCREEN)
|
||||
removeAttribute(player, ATTRIBUTE_FLIGHT_PROGRESS)
|
||||
removeAttributes(
|
||||
player, ATTRIBUTE_BALLOON_TOP,
|
||||
ATTRIBUTE_BALLOON_BOTTOM,
|
||||
ATTRIBUTE_FLIGHT_DESTINATION,
|
||||
ATTRIBUTE_FLIGHT_SCREEN,
|
||||
ATTRIBUTE_FLIGHT_PROGRESS
|
||||
)
|
||||
}
|
||||
|
||||
private fun playControlSound(player: Player, buttonId: Int) {
|
||||
|
|
@ -489,19 +493,11 @@ class BalloonFlightInterface : InterfaceListener {
|
|||
whileBlack()
|
||||
closeOverlay(player)
|
||||
openOverlay(player, Components.FADE_FROM_BLACK_170)
|
||||
submitIndividualPulse(player, object : Pulse() {
|
||||
var counter = 0
|
||||
override fun pulse(): Boolean {
|
||||
when (counter++) {
|
||||
3 -> {
|
||||
closeOverlay(player)
|
||||
unlock(player)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
queueScript(player, 3, QueueStrength.SOFT) {
|
||||
closeOverlay(player)
|
||||
unlock(player)
|
||||
return@queueScript stopExecuting(player)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import core.api.animationDuration
|
|||
import core.api.closeInterface
|
||||
import core.api.findLocalNPC
|
||||
import core.api.getAttribute
|
||||
import core.api.getStatLevel
|
||||
import core.api.getVarbit
|
||||
import core.api.hasLevelStat
|
||||
import core.api.isQuestComplete
|
||||
import core.api.lock
|
||||
import core.api.openDialogue
|
||||
|
|
@ -100,7 +100,7 @@ class BalloonTravelInterface : InterfaceListener, InteractionListener {
|
|||
return false
|
||||
}
|
||||
|
||||
if (getStatLevel(player, Skills.FIREMAKING) < destination.requiredFiremakingLevel) {
|
||||
if (!hasLevelStat(player, Skills.FIREMAKING, destination.requiredFiremakingLevel)) {
|
||||
sendMessage(
|
||||
player,
|
||||
"You require a Firemaking level of ${destination.requiredFiremakingLevel} to travel there."
|
||||
|
|
@ -138,7 +138,7 @@ class BalloonTravelInterface : InterfaceListener, InteractionListener {
|
|||
return false
|
||||
}
|
||||
|
||||
if (getStatLevel(player, Skills.FIREMAKING) < destination.requiredFiremakingLevel) {
|
||||
if (!hasLevelStat(player, Skills.FIREMAKING, destination.requiredFiremakingLevel)) {
|
||||
sendMessage(
|
||||
player,
|
||||
"You require a Firemaking level of ${destination.requiredFiremakingLevel} to travel there."
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@ import content.data.Quests
|
|||
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.STAGE_FLIGHT
|
||||
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.VARBIT_ENTRANA_BALLOON
|
||||
import core.api.face
|
||||
import core.api.playJingle
|
||||
import core.api.setQuestStage
|
||||
import core.api.setVarbit
|
||||
import core.game.activity.Cutscene
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.MusicContext
|
||||
import core.net.packet.out.MusicPacket
|
||||
import org.rs09.consts.NPCs
|
||||
|
||||
/**
|
||||
|
|
@ -41,7 +39,7 @@ class BalloonConstructionCutscene(player: Player) : Cutscene(player) {
|
|||
val obj = getObject(55, 26)!!
|
||||
face(player, obj.location)
|
||||
fadeFromBlack()
|
||||
PacketRepository.send(MusicPacket::class.java, MusicContext(player, 116, true))
|
||||
playJingle(player, 116)
|
||||
setVarbit(player, VARBIT_ENTRANA_BALLOON, 2, true)
|
||||
timedUpdate(5)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package content.region.misc.entrana.quest.enlightenedjourney.cutscene
|
|||
import content.data.Quests
|
||||
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.STAGE_FIRST_EXPERIMENT
|
||||
import core.api.face
|
||||
import core.api.playJingle
|
||||
import core.api.setQuestStage
|
||||
import core.api.visualize
|
||||
import core.game.activity.Cutscene
|
||||
|
|
@ -11,9 +12,6 @@ import core.game.node.entity.impl.Projectile
|
|||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.MusicContext
|
||||
import core.net.packet.out.MusicPacket
|
||||
import org.rs09.consts.NPCs
|
||||
|
||||
/**
|
||||
|
|
@ -41,7 +39,7 @@ class BalloonExperimentCutscene(player: Player) : Cutscene(player) {
|
|||
rotateCamera(54, 18, 400)
|
||||
addNPC(NPCs.AUGUSTE_5049, 57, 27, Direction.SOUTH_WEST)
|
||||
fadeFromBlack()
|
||||
PacketRepository.send(MusicPacket::class.java, MusicContext(player, 116, true))
|
||||
playJingle(player, 116)
|
||||
timedUpdate(6)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import content.data.Quests
|
|||
import content.region.misc.entrana.quest.enlightenedjourney.quest.EnlightenedJourney.Companion.STAGE_SECOND_EXPERIMENT
|
||||
import core.api.animate
|
||||
import core.api.face
|
||||
import core.api.playJingle
|
||||
import core.api.sendChat
|
||||
import core.api.setQuestStage
|
||||
import core.api.visualize
|
||||
|
|
@ -13,9 +14,6 @@ import core.game.node.entity.impl.Projectile
|
|||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Direction
|
||||
import core.game.world.map.Location
|
||||
import core.net.packet.PacketRepository
|
||||
import core.net.packet.context.MusicContext
|
||||
import core.net.packet.out.MusicPacket
|
||||
import org.rs09.consts.NPCs
|
||||
|
||||
/**
|
||||
|
|
@ -44,7 +42,7 @@ class SecondBalloonExperimentCutscene(player: Player) : Cutscene(player) {
|
|||
rotateCamera(54, 18, 400)
|
||||
addNPC(NPCs.AUGUSTE_5049, 57, 27, Direction.SOUTH_WEST)
|
||||
fadeFromBlack()
|
||||
PacketRepository.send(MusicPacket::class.java, MusicContext(player, 116, true))
|
||||
playJingle(player, 116)
|
||||
timedUpdate(6)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ package content.region.misc.entrana.quest.enlightenedjourney.quest
|
|||
import content.data.Quests
|
||||
import core.api.addItemOrDrop
|
||||
import core.api.getAttribute
|
||||
import core.api.getQuestPoints
|
||||
import core.api.getQuestStage
|
||||
import core.api.getStatLevel
|
||||
import core.api.hasLevelStat
|
||||
import core.api.inInventory
|
||||
import core.api.removeAttribute
|
||||
import core.api.removeAttributes
|
||||
import core.api.rewardXP
|
||||
import core.api.sendItemZoomOnInterface
|
||||
import core.api.setInterfaceText
|
||||
import core.api.setVarbit
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.link.quest.Quest
|
||||
|
|
@ -93,10 +96,10 @@ class EnlightenedJourney : Quest(Quests.ENLIGHTENED_JOURNEY, 55, 54, 1, 0, VARBI
|
|||
if (stage == 0) {
|
||||
line(player, "I can start this quest by speaking to !!Auguste?? on !!Entrana??.", line++)
|
||||
line(player, "Minimum Requirements:", line++)
|
||||
line(player, "Level 36 Crafting", line++, getStatLevel(player, Skills.CRAFTING) >= 36)
|
||||
line(player, "Level 30 Farming", line++, getStatLevel(player, Skills.FARMING) >= 30)
|
||||
line(player, "Level 20 Firemaking", line++, getStatLevel(player, Skills.FIREMAKING) >= 20)
|
||||
line(player, "20 Quest Points", line, core.api.getQuestPoints(player) >= 20)
|
||||
line(player, "Level 36 Crafting", line++, hasLevelStat(player, Skills.CRAFTING, 36))
|
||||
line(player, "Level 30 Farming", line++, hasLevelStat(player, Skills.FARMING, 30))
|
||||
line(player, "Level 20 Firemaking", line++, hasLevelStat(player, Skills.FIREMAKING, 20))
|
||||
line(player, "20 Quest Points", line, getQuestPoints(player) >= 20)
|
||||
}
|
||||
|
||||
if (stage >= STAGE_QUEST_STARTED) {
|
||||
|
|
@ -178,18 +181,18 @@ class EnlightenedJourney : Quest(Quests.ENLIGHTENED_JOURNEY, 55, 54, 1, 0, VARBI
|
|||
}
|
||||
|
||||
override fun hasRequirements(player: Player): Boolean {
|
||||
return getStatLevel(player, Skills.CRAFTING) >= 36 &&
|
||||
getStatLevel(player, Skills.FARMING) >= 30 &&
|
||||
getStatLevel(player, Skills.FIREMAKING) >= 20 &&
|
||||
core.api.getQuestPoints(player) >= 20
|
||||
return hasLevelStat(player, Skills.CRAFTING, 36) &&
|
||||
hasLevelStat(player, Skills.FARMING, 30) &&
|
||||
hasLevelStat(player, Skills.FIREMAKING, 20) &&
|
||||
getQuestPoints(player) >= 20
|
||||
}
|
||||
|
||||
override fun finish(player: Player) {
|
||||
super.finish(player)
|
||||
var line = 10
|
||||
|
||||
player.packetDispatch.sendString("You have completed Enlightened Journey!", 277, 4)
|
||||
player.packetDispatch.sendItemZoomOnInterface(Items.BOMBER_CAP_9945, 230, 277, 5)
|
||||
setInterfaceText(player, "You have completed Enlightened Journey!", 277, 4)
|
||||
sendItemZoomOnInterface(player, 277, 5, Items.BOMBER_CAP_9945)
|
||||
|
||||
drawReward(player, "1 Quest Point", line++)
|
||||
drawReward(player, "2,000 Crafting XP", line++)
|
||||
|
|
@ -220,14 +223,16 @@ class EnlightenedJourney : Quest(Quests.ENLIGHTENED_JOURNEY, 55, 54, 1, 0, VARBI
|
|||
setVarbit(player, VARBIT_GRANDTREE_BALLOON, 0, true)
|
||||
setVarbit(player, VARBIT_CRAFTINGGUILD_BALLOON, 0, true)
|
||||
setVarbit(player, VARBIT_VARROCK_BALLOON, 0, true)
|
||||
removeAttribute(player, ATTRIBUTE_YELLOW_DYE_DELIVERED)
|
||||
removeAttribute(player, ATTRIBUTE_RED_DYE_DELIVERED)
|
||||
removeAttribute(player, ATTRIBUTE_SANDBAGS_DELIVERED)
|
||||
removeAttribute(player, ATTRIBUTE_SILK_DELIVERED)
|
||||
removeAttribute(player, ATTRIBUTE_BOWL_DELIVERED)
|
||||
removeAttribute(player, ATTRIBUTE_SAPLING_PLANTED)
|
||||
removeAttribute(player, ATTRIBUTE_TAVERLEY_AUGUSTE_REPLACED)
|
||||
removeAttribute(player, ATTRIBUTE_AUGUSTE_POST_EXPLANATION)
|
||||
removeAttributes(
|
||||
player, ATTRIBUTE_YELLOW_DYE_DELIVERED,
|
||||
ATTRIBUTE_RED_DYE_DELIVERED,
|
||||
ATTRIBUTE_SANDBAGS_DELIVERED,
|
||||
ATTRIBUTE_SILK_DELIVERED,
|
||||
ATTRIBUTE_BOWL_DELIVERED,
|
||||
ATTRIBUTE_SAPLING_PLANTED,
|
||||
ATTRIBUTE_TAVERLEY_AUGUSTE_REPLACED,
|
||||
ATTRIBUTE_AUGUSTE_POST_EXPLANATION
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateVarps(player: Player) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import core.game.node.item.Item
|
|||
import core.game.world.map.zone.ZoneBorders
|
||||
import org.rs09.consts.Items
|
||||
import org.rs09.consts.NPCs
|
||||
import org.rs09.consts.Scenery
|
||||
|
||||
/**
|
||||
* Listeners for the Enlightened Journey quest.
|
||||
|
|
@ -66,8 +67,13 @@ class EnlightenedJourneyListeners : InteractionListener, MapArea {
|
|||
return@onUseWith true
|
||||
}
|
||||
|
||||
/** Make sandbag from sandpit. Entrana/Zanaris, Rellekka, Yanille. */
|
||||
onUseWith(IntType.SCENERY, Items.EMPTY_SACK_5418, 2645, 4373, 10814) { player, _, _ ->
|
||||
/** Make sandbag from sandpit. Entrana/Zanaris, Rellekka, Yanille, Dorgesh-Kaan. */
|
||||
onUseWith(IntType.SCENERY, Items.EMPTY_SACK_5418,
|
||||
Scenery.SAND_PIT_2645,
|
||||
Scenery.SAND_PIT_4373,
|
||||
Scenery.SANDPIT_10814,
|
||||
Scenery.SANDPIT_22726
|
||||
) { player, _, _ ->
|
||||
if (removeItem(player, Item(Items.EMPTY_SACK_5418, 1))) {
|
||||
animate(player, 5155)
|
||||
addItem(player, Items.SANDBAG_9943, 1)
|
||||
|
|
@ -89,7 +95,7 @@ class EnlightenedJourneyListeners : InteractionListener, MapArea {
|
|||
}
|
||||
|
||||
/** Using willow branches on the basket frame to construct the Entrana balloon. */
|
||||
onUseWith(IntType.SCENERY, Items.WILLOW_BRANCH_5933, 19132) { player, _, _ ->
|
||||
onUseWith(IntType.SCENERY, Items.WILLOW_BRANCH_5933, Scenery.BASKET_19132) { player, _, _ ->
|
||||
if (getQuestStage(player, Quests.ENLIGHTENED_JOURNEY) != STAGE_WILLOW_BASKET) {
|
||||
return@onUseWith false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue