mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-13 17:28:57 +07:00
Empire overview resources (#6442)
* Empire Overview Resources: Vertical option * Empire Overview Resources: Extra Info * Empire Overview Resources: Tweaks Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
BIN
android/Images/OtherIcons/Turn right.png
Normal file
BIN
android/Images/OtherIcons/Turn right.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
@ -765,7 +765,6 @@ Territory = Territorium
|
||||
Force = Kampfkraft
|
||||
GOLDEN AGE = GOLDENES ZEITALTER
|
||||
Golden Age = Goldenes Zeitalter
|
||||
We Love The King Day = 'Wir lieben den König'-Tag
|
||||
Global Effect = Globaler Effekt
|
||||
[year] BC = [year] v. Chr.
|
||||
[year] AD = [year] n. Chr.
|
||||
@ -983,6 +982,15 @@ Far away = Weit entfernt
|
||||
Status = Status
|
||||
Location = Standort
|
||||
|
||||
Unimproved = Unverbessert
|
||||
Number of tiles with this resource\nin your territory, without an\nappropriate improvement to use it = Anzahl der Felder mit dieser\nRessource innerhalb Deines Territoriums,\ndenen die entsprechende\nVerbesserung zu ihrer Nutzung fehlt.
|
||||
We Love The King Day = 'Wir lieben den König'-Tag
|
||||
WLTK+ = WLDK+
|
||||
Number of your cities celebrating\n'We Love The King Day' thanks\nto access to this resource = Anzahl Deiner Städte, die den\n'Wir lieben den König'-Tag feiern,\ndank Zugang zu dieser Ressource.
|
||||
WLTK demand = Bedarf für WLDK-Tag
|
||||
WLTK- = WLDK-
|
||||
Number of your cities\ndemanding this resource for\n'We Love The King Day' = Anzahl Deiner Städte, die nach\ndieser Ressource verlangen, um\nden 'Wir lieben den König'-Tag\nfeiern zu können.
|
||||
|
||||
# Victory
|
||||
|
||||
Science victory = Wissenschaftssieg
|
||||
@ -5393,8 +5401,8 @@ The Maya measured time in days from what we would call 11th of August, 3114 BCE.
|
||||
Unciv only displays ය B'ak'tuns, ඹ K'atuns and ම Tuns (from left to right) since that is enough to approximate gregorian calendar years. The Maya numerals are pretty obvious to understand. Have fun deciphering them! = Unciv zeigt nur ය B'ak'tuns, ඹ K'atuns und ම Tuns (von links nach rechts) an, da dies ausreicht, um gregorianische Kalenderjahre anzunähern. Die Maya-Ziffern sind ziemlich einfach zu verstehen. Viel Spaß beim Entschlüsseln!
|
||||
|
||||
Your cities will periodically demand different luxury goods to satisfy their desire for new things in life. = Deine Städte werden in regelmäßigen Abständen verschiedene Luxusgüter verlangen, um ihren Wunsch nach neuen Dingen im Leben zu befriedigen.
|
||||
If you manage to acquire the demanded luxury by trade, expansion, or conquest, the city will celebrate We Love The King Day for 20 turns. = Wenn es dir gelingt, den geforderten Luxus durch Handel, Expansion oder Eroberung zu erwerben, wird die Stadt 20 Runden lang den Wir Lieben Den König Tag feiern.
|
||||
During the We Love The King Day, the city will grow 25% faster. = Während des Wir Lieben Den König Tags wird die Stadt um 25 % schneller wachsen.
|
||||
If you manage to acquire the demanded luxury by trade, expansion, or conquest, the city will celebrate We Love The King Day for 20 turns. = Wenn es dir gelingt, den geforderten Luxus durch Handel, Expansion oder Eroberung zu erwerben, wird die Stadt 20 Runden lang den 'Wir Lieben Den König'-Tag feiern.
|
||||
During the We Love The King Day, the city will grow 25% faster. = Während des 'Wir Lieben Den König'-Tags wird die Stadt um 25 % schneller wachsen.
|
||||
This means exploration and trade is important to grow your cities! = Das bedeutet, dass Erkundung und Handel wichtig sind, um deine Städte wachsen zu lassen!
|
||||
|
||||
|
||||
|
@ -770,7 +770,6 @@ Territory =
|
||||
Force =
|
||||
GOLDEN AGE =
|
||||
Golden Age =
|
||||
We Love The King Day =
|
||||
Global Effect =
|
||||
[year] BC =
|
||||
[year] AD =
|
||||
@ -988,6 +987,14 @@ Somewhere around [city] =
|
||||
Far away =
|
||||
Status =
|
||||
Location =
|
||||
Unimproved =
|
||||
Number of tiles with this resource\nin your territory, without an\nappropriate improvement to use it =
|
||||
We Love The King Day =
|
||||
WLTK+ =
|
||||
Number of your cities celebrating\n'We Love The King Day' thanks\nto access to this resource =
|
||||
WLTK demand =
|
||||
WLTK- =
|
||||
Number of your cities\ndemanding this resource for\n'We Love The King Day' =
|
||||
|
||||
# Victory
|
||||
|
||||
|
@ -44,8 +44,8 @@ enum class EmpireOverviewCategories(
|
||||
= DiplomacyOverviewTab(viewingPlayer, overviewScreen, persistedData),
|
||||
fun (viewingPlayer: CivilizationInfo) = viewingPlayer.diplomacy.isEmpty().toState()),
|
||||
Resources("StatIcons/Happiness", 'R', Align.topLeft,
|
||||
fun (viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen, _: EmpireOverviewTabPersistableData?)
|
||||
= ResourcesOverviewTab(viewingPlayer, overviewScreen),
|
||||
fun (viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?)
|
||||
= ResourcesOverviewTab(viewingPlayer, overviewScreen, persistedData),
|
||||
fun (viewingPlayer: CivilizationInfo) = viewingPlayer.detailedCivResources.isEmpty().toState()),
|
||||
Religion("StatIcons/Faith", 'F', Align.top,
|
||||
fun (viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?)
|
||||
|
@ -1,75 +1,222 @@
|
||||
package com.unciv.ui.overviewscreen
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.models.ruleset.tile.ResourceSupplyList
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.tile.TileResource
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.utils.ImageGetter
|
||||
import com.unciv.ui.utils.addSeparator
|
||||
import com.unciv.ui.utils.onClick
|
||||
import com.unciv.ui.utils.toLabel
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
||||
|
||||
|
||||
class ResourcesOverviewTab(
|
||||
viewingPlayer: CivilizationInfo,
|
||||
overviewScreen: EmpireOverviewScreen
|
||||
overviewScreen: EmpireOverviewScreen,
|
||||
persistedData: EmpireOverviewTabPersistableData? = null
|
||||
) : EmpireOverviewTab(viewingPlayer, overviewScreen) {
|
||||
class ResourcesTabPersistableData(
|
||||
var vertical: Boolean = false
|
||||
) : EmpireOverviewTabPersistableData() {
|
||||
override fun isEmpty() = !vertical
|
||||
}
|
||||
override val persistableData = (persistedData as? ResourcesTabPersistableData) ?: ResourcesTabPersistableData()
|
||||
|
||||
init {
|
||||
defaults().pad(10f)
|
||||
companion object {
|
||||
private const val iconSize = 50f
|
||||
private const val defaultPad = 10f
|
||||
private const val tooltipSize = 24f
|
||||
}
|
||||
|
||||
val resourceDrilldown = viewingPlayer.detailedCivResources
|
||||
private fun getTurnImage(vertical: Boolean) =
|
||||
ImageGetter.getImage("OtherIcons/Turn right")
|
||||
.apply {
|
||||
color = ImageGetter.getBlue()
|
||||
if (vertical)
|
||||
rotateBy(90f)
|
||||
}
|
||||
.surroundWithCircle(iconSize, color = Color.LIGHT_GRAY)
|
||||
private val turnImageH = getTurnImage(false)
|
||||
private val turnImageV = getTurnImage(true)
|
||||
|
||||
// First row of table has all the icons
|
||||
add()
|
||||
private val resourceDrilldown: ResourceSupplyList = viewingPlayer.detailedCivResources
|
||||
private val extraDrilldown: ResourceSupplyList = getExtraDrilldown()
|
||||
private val drilldownSequence = resourceDrilldown.asSequence() + extraDrilldown.asSequence()
|
||||
|
||||
// Order of source ResourceSupplyList: by tiles, enumerating the map in that spiral pattern
|
||||
// UI should not surprise player, thus we need a deterministic and guessable order
|
||||
val resources = resourceDrilldown.map { it.resource }
|
||||
.filter { it.resourceType != ResourceType.Bonus }.distinct()
|
||||
.sortedWith(compareBy({ it.resourceType }, { it.name.tr() }))
|
||||
private val resources: List<TileResource> = drilldownSequence
|
||||
.map { it.resource }
|
||||
.filter { it.resourceType != ResourceType.Bonus }
|
||||
.distinct()
|
||||
.sortedWith(
|
||||
compareBy<TileResource> { it.resourceType }
|
||||
.thenBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.name.tr() }
|
||||
)
|
||||
.toList()
|
||||
private val origins: List<String> = resourceDrilldown.asSequence()
|
||||
.map { it.origin }.distinct().toList()
|
||||
private val extraOrigins: List<ExtraInfoOrigin> = extraDrilldown.asSequence()
|
||||
.mapNotNull { ExtraInfoOrigin.safeValueOf(it.origin) }.distinct().toList()
|
||||
|
||||
for (resource in resources) {
|
||||
// Create a group of label and icon for each resource.
|
||||
val resourceImage = ImageGetter.getResourceImage(resource.name, 50f)
|
||||
val labelPadding = 10f
|
||||
// Using a table here leads to spacing issues
|
||||
// due to different label lengths.
|
||||
val holder = Group()
|
||||
resourceImage.onClick {
|
||||
viewingPlayer.gameInfo.notifyExploredResources(viewingPlayer, resource.name, 0, true)
|
||||
private fun ResourceSupplyList.getLabel(resource: TileResource, origin: String): Label? =
|
||||
firstOrNull { it.resource == resource && it.origin == origin }?.amount?.toLabel()
|
||||
private fun ResourceSupplyList.getTotalLabel(resource: TileResource): Label =
|
||||
filter { it.resource == resource }.sumOf { it.amount }.toLabel()
|
||||
private fun getResourceImage(name: String) =
|
||||
ImageGetter.getResourceImage(name, iconSize).apply {
|
||||
onClick {
|
||||
viewingPlayer.gameInfo.notifyExploredResources(viewingPlayer, name, 0, true)
|
||||
overviewScreen.game.setWorldScreen()
|
||||
}
|
||||
holder.addActor(resourceImage)
|
||||
holder.setSize(resourceImage.width, resourceImage.height + labelPadding)
|
||||
// Center-align all labels, but right-align the last couple resources' labels
|
||||
// because they may get clipped otherwise. The leftmost label should be fine
|
||||
// center-aligned (if there are more than 2 resources), because the left side
|
||||
// has more padding.
|
||||
val alignFactor = when {
|
||||
(resources.indexOf(resource) + 2 >= resources.count()) -> 1
|
||||
else -> 2
|
||||
}
|
||||
add(holder)
|
||||
|
||||
private enum class ExtraInfoOrigin(
|
||||
val horizontalCaption: String,
|
||||
val verticalCaption: String,
|
||||
val tooltip: String
|
||||
) {
|
||||
Unimproved("Unimproved", "Unimproved",
|
||||
"Number of tiles with this resource\nin your territory, without an\nappropriate improvement to use it"),
|
||||
CelebratingWLKT("We Love The King Day", "WLTK+",
|
||||
"Number of your cities celebrating\n'We Love The King Day' thanks\nto access to this resource"),
|
||||
DemandingWLTK("WLTK demand", "WLTK-",
|
||||
"Number of your cities\ndemanding this resource for\n'We Love The King Day'"),
|
||||
;
|
||||
companion object {
|
||||
fun safeValueOf(name: String) = values().firstOrNull { it.name == name }
|
||||
}
|
||||
}
|
||||
private val fixedContent = Table()
|
||||
|
||||
init {
|
||||
defaults().pad(defaultPad)
|
||||
fixedContent.defaults().pad(defaultPad)
|
||||
|
||||
turnImageH.onClick {
|
||||
persistableData.vertical = true
|
||||
update()
|
||||
}
|
||||
turnImageV.onClick {
|
||||
persistableData.vertical = false
|
||||
update()
|
||||
}
|
||||
|
||||
update()
|
||||
}
|
||||
|
||||
override fun getFixedContent() = fixedContent
|
||||
|
||||
private fun update() {
|
||||
clear()
|
||||
fixedContent.clear()
|
||||
if (persistableData.vertical) updateVertical()
|
||||
else updateHorizontal()
|
||||
}
|
||||
|
||||
private fun updateHorizontal() {
|
||||
// First row of table has all the icons
|
||||
add(turnImageH)
|
||||
for (resource in resources) {
|
||||
add(getResourceImage(resource.name).apply {
|
||||
addTooltip(resource.name, tipAlign = Align.topLeft)
|
||||
})
|
||||
}
|
||||
addSeparator()
|
||||
|
||||
val origins = resourceDrilldown.map { it.origin }.distinct()
|
||||
// One detail row per origin
|
||||
for (origin in origins) {
|
||||
add(origin.toLabel()).left()
|
||||
for (resource in resources) {
|
||||
add(resourceDrilldown.getLabel(resource, origin))
|
||||
}
|
||||
row()
|
||||
}
|
||||
addSeparator(Color.GRAY).pad(0f, defaultPad)
|
||||
|
||||
// One row for the totals
|
||||
add("Total".toLabel()).left()
|
||||
for (resource in resources) {
|
||||
add(resourceDrilldown.getTotalLabel(resource))
|
||||
}
|
||||
addSeparator()
|
||||
|
||||
// Separate rows for origins not part of the totals
|
||||
for (origin in extraOrigins) {
|
||||
add(origin.horizontalCaption.toLabel().apply {
|
||||
addTooltip(origin.tooltip, tooltipSize, tipAlign = Align.left)
|
||||
}).left()
|
||||
for (resource in resources) {
|
||||
add(extraDrilldown.getLabel(resource, origin.name))
|
||||
}
|
||||
row()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVertical() {
|
||||
// First row of table has all the origin labels
|
||||
fixedContent.apply {
|
||||
add(turnImageV).size(iconSize)
|
||||
add()
|
||||
addSeparatorVertical(Color.GRAY).pad(0f)
|
||||
for (origin in origins) {
|
||||
add(origin.toLabel())
|
||||
}
|
||||
add("Total".toLabel())
|
||||
addSeparatorVertical(Color.GRAY).pad(0f)
|
||||
for (origin in extraOrigins) {
|
||||
add(origin.verticalCaption.toLabel().apply {
|
||||
addTooltip(origin.tooltip, tooltipSize, targetAlign = Align.bottom, tipAlign = Align.topRight)
|
||||
})
|
||||
}
|
||||
addSeparator().pad(0f, defaultPad)
|
||||
}
|
||||
|
||||
// One detail row per resource
|
||||
for (resource in resources) {
|
||||
val resourceSupply = resourceDrilldown.firstOrNull { it.resource == resource && it.origin == origin }
|
||||
if (resourceSupply == null) add()
|
||||
else add(resourceSupply.amount.toString().toLabel())
|
||||
add(getResourceImage(resource.name))
|
||||
add(resource.name.toLabel())
|
||||
addSeparatorVertical(Color.GRAY).pad(0f)
|
||||
for (origin in origins) {
|
||||
add(resourceDrilldown.getLabel(resource, origin))
|
||||
}
|
||||
add(resourceDrilldown.getTotalLabel(resource))
|
||||
addSeparatorVertical(Color.GRAY).pad(0f)
|
||||
for (origin in extraOrigins) {
|
||||
add(extraDrilldown.getLabel(resource, origin.name))
|
||||
}
|
||||
row()
|
||||
}
|
||||
|
||||
add("Total".toLabel())
|
||||
for (resource in resources) {
|
||||
val sum = resourceDrilldown.filter { it.resource == resource }.sumOf { it.amount }
|
||||
add(sum.toLabel())
|
||||
equalizeColumns(fixedContent, this)
|
||||
overviewScreen.resizePage(this) // Without the height is miscalculated - shouldn't be
|
||||
}
|
||||
|
||||
private fun getExtraDrilldown(): ResourceSupplyList {
|
||||
val resourceSupplyList = ResourceSupplyList()
|
||||
for (city in viewingPlayer.cities) {
|
||||
if (city.demandedResource.isEmpty()) continue
|
||||
val wltkResource = gameInfo.ruleSet.tileResources[city.demandedResource] ?: continue
|
||||
if (city.isWeLoveTheKingDayActive()) {
|
||||
resourceSupplyList.add(wltkResource, 1, ExtraInfoOrigin.CelebratingWLKT.name)
|
||||
} else {
|
||||
resourceSupplyList.add(wltkResource, 1, ExtraInfoOrigin.DemandingWLTK.name)
|
||||
}
|
||||
for (tile in city.getTiles()) {
|
||||
if (tile.isCityCenter()) continue
|
||||
if (!tile.hasViewableResource(viewingPlayer)) continue
|
||||
val tileResource = tile.tileResource
|
||||
if (tileResource.resourceType == ResourceType.Bonus) continue
|
||||
if (tile.improvement == tileResource.improvement) continue
|
||||
if (tileResource.resourceType == ResourceType.Strategic && tile.getTileImprovement()?.isGreatImprovement() == true) continue
|
||||
resourceSupplyList.add(tileResource, 1, ExtraInfoOrigin.Unimproved.name)
|
||||
}
|
||||
}
|
||||
return resourceSupplyList
|
||||
}
|
||||
}
|
||||
|
@ -96,10 +96,10 @@ class WonderOverviewTab(
|
||||
row()
|
||||
}
|
||||
|
||||
top()
|
||||
defaults().pad(10f).align(Align.center)
|
||||
(1..5).forEach { _ -> add() } // dummies so equalizeColumns can work because the first grid cell is colspan(5)
|
||||
row()
|
||||
top()
|
||||
|
||||
createGrid()
|
||||
|
||||
|
@ -23,6 +23,7 @@ import com.unciv.models.translations.tr
|
||||
* @param forceContentSize Force virtual [content] width/height for alignment calculation
|
||||
* - because Gdx auto layout reports wrong dimensions on scaled actors.
|
||||
*/
|
||||
// region fields
|
||||
class UncivTooltip <T: Actor>(
|
||||
val target: Actor,
|
||||
val content: T,
|
||||
@ -33,7 +34,6 @@ class UncivTooltip <T: Actor>(
|
||||
forceContentSize: Vector2? = null,
|
||||
) : InputListener() {
|
||||
|
||||
// region fields
|
||||
private val container: Container<T> = Container(content)
|
||||
enum class TipState { Hidden, Showing, Shown, Hiding }
|
||||
/** current visibility state of the Tooltip */
|
||||
@ -49,7 +49,9 @@ class UncivTooltip <T: Actor>(
|
||||
contentHeight = forceContentSize?.y ?: content.height
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region show, hide and positioning
|
||||
|
||||
/** Show the Tooltip ([immediate]ly or begin the animation). _Can_ be called programmatically. */
|
||||
fun show(immediate: Boolean = false) {
|
||||
if (target.stage == null) return
|
||||
@ -129,9 +131,10 @@ class UncivTooltip <T: Actor>(
|
||||
}
|
||||
private fun Actor.getEdgePoint(align: Int) =
|
||||
Vector2(getOriginX(width,align),getOriginY(height,align))
|
||||
//endregion
|
||||
|
||||
//endregion
|
||||
//region events
|
||||
|
||||
override fun enter(event: InputEvent?, x: Float, y: Float, pointer: Int, fromActor: Actor?) {
|
||||
// assert(event?.listenerActor == target) - tested - holds true
|
||||
if (fromActor != null && fromActor.isDescendantOf(target)) return
|
||||
@ -147,6 +150,7 @@ class UncivTooltip <T: Actor>(
|
||||
container.toFront() // this is a no-op if it has no parent
|
||||
return super.touchDown(event, x, y, pointer, button)
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
companion object {
|
||||
@ -158,9 +162,16 @@ class UncivTooltip <T: Actor>(
|
||||
* @param text Automatically translated tooltip text
|
||||
* @param size _Vertical_ size of the entire Tooltip including background
|
||||
* @param always override requirement: presence of physical keyboard
|
||||
* @param tipAlign Point on the Tooltip to align with the top right of the [target]
|
||||
* @param targetAlign Point on the [target] widget to align the Tooltip to
|
||||
* @param tipAlign Point on the Tooltip to align with the given point on the [target]
|
||||
*/
|
||||
fun Actor.addTooltip(text: String, size: Float = 26f, always: Boolean = false, tipAlign: Int = Align.top) {
|
||||
fun Actor.addTooltip(
|
||||
text: String,
|
||||
size: Float = 26f,
|
||||
always: Boolean = false,
|
||||
targetAlign: Int = Align.topRight,
|
||||
tipAlign: Int = Align.top
|
||||
) {
|
||||
if (!(always || KeyPressDispatcher.keyboardAvailable) || text.isEmpty()) return
|
||||
|
||||
val label = text.toLabel(ImageGetter.getBlue(), 38)
|
||||
@ -175,18 +186,20 @@ class UncivTooltip <T: Actor>(
|
||||
background.setPadding(4f+skewPadDescenders, horizontalPad, 8f-skewPadDescenders, horizontalPad)
|
||||
|
||||
val widthHeightRatio: Float
|
||||
val multiRowSize = size * (1 + text.count { it == '\n' })
|
||||
val labelWithBackground = Container(label).apply {
|
||||
setBackground(background)
|
||||
pack()
|
||||
widthHeightRatio = width / height
|
||||
isTransform = true // otherwise setScale is ignored
|
||||
setScale(size / height)
|
||||
setScale(multiRowSize / height)
|
||||
}
|
||||
|
||||
addListener(UncivTooltip(this,
|
||||
labelWithBackground,
|
||||
forceContentSize = Vector2(size * widthHeightRatio, size),
|
||||
offset = Vector2(-size/4, size/4),
|
||||
forceContentSize = Vector2(multiRowSize * widthHeightRatio, multiRowSize),
|
||||
offset = Vector2(-multiRowSize/4, size/4),
|
||||
targetAlign = targetAlign,
|
||||
tipAlign = tipAlign
|
||||
))
|
||||
}
|
||||
|
@ -677,6 +677,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
|
||||
* [favor](https://thenounproject.com/icon/favor-1029350/) by MICHAEL G BROWN for WLTK marker on City Overview
|
||||
* [Party](https://thenounproject.com/icon/party-1784941/) by Adrien Coquet for WLTK header on City Overview
|
||||
* [Party](https://thenounproject.com/icon/party-2955155/) by Lars Meiertoberens as additional WLKT decoration
|
||||
* [turn right](https://thenounproject.com/icon/turn-right-1920867/) by Alice Design for Resource Overview
|
||||
|
||||
## Main menu
|
||||
|
||||
|
Reference in New Issue
Block a user