package uk.co.nickthecoder.gamescupboard.client.view
import com.soywiz.klock.seconds
import com.soywiz.korge.input.mouse
import com.soywiz.korge.tween.get
import com.soywiz.korge.view.*
import com.soywiz.korim.color.RGBA
import com.soywiz.korma.geom.Point
import com.soywiz.korma.geom.XY
import uk.co.nickthecoder.gamescupboard.client.*
import uk.co.nickthecoder.gamescupboard.common.Player
import uk.co.nickthecoder.gamescupboard.common.playingAreaHeight
import uk.co.nickthecoder.gamescupboard.common.playingAreaWidth
import kotlin.math.max
import kotlin.math.min
// Offsets for the speech bubbles
private const val avatarMarginX = 10
private const val avatarMarginY = 10
private const val defaultWidth = 100.0
private const val defaultHeight = 40.0
private const val borderSize = 2.0
class AvatarView(
id: Int,
val player: Player,
bgColor: RGBA,
/**
* 0 = Top (Chat below)
* 1 = Right (Chat left)
* 2 = Bottom (Chat above)
* 3 = Left (Chat right)
*/
val side: Int = 0
) : GameObjectView(id) {
private val border = if (player.id == localPlayerId()) {
RoundRect(
defaultWidth + borderSize * 2,
defaultHeight + borderSize * 2,
rx = 6.0 + borderSize,
fill = avatarBorderColor
).apply {
position(- width / 2, - height / 2)
this@AvatarView.addChild(this)
}
} else {
null
}
private val background = roundRect(defaultWidth, defaultHeight, rx = 6.0, fill = bgColor) {
position(- width / 2, - height / 2)
}
private val textView = text(player.name, color = avatarTextColor) {
position(- width / 2, - height / 2)
}
init {
this.mouse.click.add { onClick() }
}
var playerName: String = player.name
set(v) {
field = v
updateText()
}
/**
* A count of the number of pieces this player has moved into their "private" area.
*
* In card games, this is the number of cards in hand.
* In Scrabble, it is the number of tiles in their rack.
*/
var privatePieceCount = 0
set(v) {
field = v
updateText()
}
private fun updateText() {
with(textView) {
val count = privatePieceCount
text = if (count == 0) {
playerName
} else {
"$playerName ($count)"
}
position(- width / 2, - height / 2)
}
}
private fun onClick() {
gamesCupboardClient.chatInput.chatTo(player)
}
override fun isTouching(point: Point): Boolean {
return point.x >= x - background.width / 2 && point.x <= x + background.width / 2 &&
point.y >= y - background.height / 2 && point.y <= y + background.height / 2
}
private fun bubblePosition(bubble: Container): XY {
val main = background
val local = when (side) {
0 -> Point(0.0, main.height / 2 + bubble.height / 2 + avatarMarginY) // Below
1 -> Point(- main.width / 2 - bubble.width / 2 - avatarMarginX, 0.0) // Right
2 -> Point(0.0, - main.height / 2 - bubble.height / 2 - avatarMarginY) // Bottom
else -> Point(main.width / 2 + bubble.width / 2 + avatarMarginX, 0.0) // Left
}
return Point(
min(playingAreaWidth - bubble.width / 2, max(bubble.width / 2, local.x + x)),
min(playingAreaHeight - bubble.height / 2, max(bubble.height / 2, local.y + y))
)
}
/**
* The bubble will be positioned based on the avatar's position.
* If it's near the top edge, then the bubble is below etc.
* The offset is based on the "main" part of the Avatar.
* Currently, this is the [background], but when/if we have graphical avatars,
* then it will be the image.
*/
fun speak(message: String, toAvatar: AvatarView? = null) {
val bubble = gamesCupboardClient.playingArea.speechBubble(message)
bubble.position(bubblePosition(bubble))
// Fade into and out of view. Then remove the bubble.
bubble.launchTween(
bubble::alpha[0.0, 1.0], time = bubbleDuration, easing = OnFinishEase(bubbleEase) {
this@AvatarView.removeChild(bubble)
}
)
if (toAvatar != null) {
val to = toAvatar.bubblePosition(bubble)
bubble.launchTween(
bubble::x[to.x], bubble::y[to.y], time = 0.5.seconds
)
}
}
}