mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-07-15 22:59:58 +00:00
multi item support
This commit is contained in:
@ -0,0 +1,2 @@
|
||||
drop index bis_piece_player_id_idx;
|
||||
create index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);
|
@ -0,0 +1,2 @@
|
||||
drop index bis_piece_player_id_idx;
|
||||
create index bis_piece_type_player_id_idx on bis(player_id, piece, piece_type);
|
@ -37,10 +37,13 @@ trait BiSHelper extends BisProviderHelper {
|
||||
}
|
||||
|
||||
def putBiS(playerId: PlayerId, link: String)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] =
|
||||
downloadBiS(link, playerId.job).flatMap { bis =>
|
||||
Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
|
||||
}.map(_ => ())
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
|
||||
(storage ? DatabaseBiSHandler.RemovePiecesFromBiS(playerId)).flatMap { _ =>
|
||||
downloadBiS(link, playerId.job).flatMap { bis =>
|
||||
Future.traverse(bis.pieces)(addPieceBiS(playerId, _))
|
||||
}.map(_ => ())
|
||||
}
|
||||
}
|
||||
|
||||
def removePieceBiS(playerId: PlayerId, piece: Piece)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Int] =
|
||||
|
@ -21,13 +21,15 @@ case class PlayerResponse(
|
||||
@Schema(description = "player loot priority", `type` = "number") priority: Option[Int]) {
|
||||
def toPlayer: Player =
|
||||
Player(-1, partyId, Job.withName(job), nick,
|
||||
BiS(bis.getOrElse(Seq.empty).map(_.toPiece)), loot.getOrElse(Seq.empty).map(_.toLoot),
|
||||
BiS(bis.getOrElse(Seq.empty).map(_.toPiece)),
|
||||
loot.getOrElse(Seq.empty).map(_.toLoot),
|
||||
link, priority.getOrElse(0))
|
||||
}
|
||||
|
||||
object PlayerResponse {
|
||||
def fromPlayer(player: Player): PlayerResponse =
|
||||
PlayerResponse(player.partyId, player.job.toString, player.nick,
|
||||
Some(player.bis.pieces.map(PieceResponse.fromPiece)), Some(player.loot.map(LootResponse.fromLoot)),
|
||||
Some(player.bis.pieces.map(PieceResponse.fromPiece)),
|
||||
Some(player.loot.map(LootResponse.fromLoot)),
|
||||
player.link, Some(player.priority))
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ object BiSView {
|
||||
input(name:="add", id:="add", `type`:="submit", value:="add")
|
||||
),
|
||||
|
||||
form(action:="/bis", method:="post")(
|
||||
form(action:=s"/party/$partyId/bis", method:="post")(
|
||||
select(name:="player", id:="player", title:="player")
|
||||
(for (player <- party) yield option(player.playerId.toString)),
|
||||
input(name:="link", id:="link", placeholder:="player bis link", title:="link", `type`:="text"),
|
||||
|
@ -47,8 +47,8 @@ class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)
|
||||
post {
|
||||
formFields("nick".as[String], "job".as[String], "priority".as[Int].?, "link".as[String].?, "action".as[String]) {
|
||||
(nick, job, maybePriority, maybeLink, action) =>
|
||||
onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) {
|
||||
case _ => redirect(s"/party/$partyId/players", StatusCodes.Found)
|
||||
onComplete(modifyPartyCall(partyId, nick, job, maybePriority, maybeLink, action)) { _ =>
|
||||
redirect(s"/party/$partyId/players", StatusCodes.Found)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ class PlayerView(override val storage: ActorRef, override val ariyala: ActorRef)
|
||||
(implicit executionContext: ExecutionContext, timeout: Timeout): Future[Unit] = {
|
||||
def maybePlayerId = PlayerId(partyId, Some(nick), Some(job))
|
||||
def player(playerId: PlayerId) =
|
||||
Player(-1, partyId, playerId.job, playerId.nick, BiS(), Seq.empty, maybeLink, maybePriority.getOrElse(0))
|
||||
Player(-1, partyId, playerId.job, playerId.nick, BiS.empty, Seq.empty, maybeLink, maybePriority.getOrElse(0))
|
||||
|
||||
(action, maybePlayerId) match {
|
||||
case ("add", Some(playerId)) => addPlayer(player(playerId)).map(_ => ())
|
||||
|
@ -8,21 +8,7 @@
|
||||
*/
|
||||
package me.arcanis.ffxivbis.models
|
||||
|
||||
case class BiS(weapon: Option[Piece],
|
||||
head: Option[Piece],
|
||||
body: Option[Piece],
|
||||
hands: Option[Piece],
|
||||
waist: Option[Piece],
|
||||
legs: Option[Piece],
|
||||
feet: Option[Piece],
|
||||
ears: Option[Piece],
|
||||
neck: Option[Piece],
|
||||
wrist: Option[Piece],
|
||||
leftRing: Option[Piece],
|
||||
rightRing: Option[Piece]) {
|
||||
|
||||
val pieces: Seq[Piece] =
|
||||
Seq(weapon, head, body, hands, waist, legs, feet, ears, neck, wrist, leftRing, rightRing).flatten
|
||||
case class BiS(pieces: Seq[Piece]) {
|
||||
|
||||
def hasPiece(piece: Piece): Boolean = piece match {
|
||||
case upgrade: PieceUpgrade => upgrades.contains(upgrade)
|
||||
@ -31,50 +17,27 @@ case class BiS(weapon: Option[Piece],
|
||||
|
||||
def upgrades: Map[PieceUpgrade, Int] =
|
||||
pieces.groupBy(_.upgrade).foldLeft(Map.empty[PieceUpgrade, Int]) {
|
||||
case (acc, (Some(k), v)) => acc + (k -> v.length)
|
||||
case (acc, (Some(k), v)) => acc + (k -> v.size)
|
||||
case (acc, _) => acc
|
||||
} withDefaultValue 0
|
||||
|
||||
def withPiece(piece: Piece): BiS = copyWithPiece(piece.piece, Some(piece))
|
||||
def withoutPiece(piece: Piece): BiS = copyWithPiece(piece.piece, None)
|
||||
def withPiece(piece: Piece): BiS = copy(pieces :+ piece)
|
||||
def withoutPiece(piece: Piece): BiS = copy(pieces.filterNot(_.strictEqual(piece)))
|
||||
|
||||
private def copyWithPiece(name: String, piece: Option[Piece]): BiS = {
|
||||
val params = Map(
|
||||
"weapon" -> weapon,
|
||||
"head" -> head,
|
||||
"body" -> body,
|
||||
"hands" -> hands,
|
||||
"waist" -> waist,
|
||||
"legs" -> legs,
|
||||
"feet" -> feet,
|
||||
"ears" -> ears,
|
||||
"neck" -> neck,
|
||||
"wrist" -> wrist,
|
||||
"left ring" -> leftRing,
|
||||
"right ring" -> rightRing
|
||||
) + (name -> piece)
|
||||
BiS(params)
|
||||
override def equals(obj: Any): Boolean = {
|
||||
def comparePieces(left: Seq[Piece], right: Seq[Piece]): Boolean =
|
||||
left.groupBy(identity).view.mapValues(_.size).forall {
|
||||
case (key, count) => right.count(_.strictEqual(key)) == count
|
||||
}
|
||||
|
||||
obj match {
|
||||
case left: BiS => comparePieces(left.pieces, pieces)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object BiS {
|
||||
def apply(data: Map[String, Option[Piece]]): BiS =
|
||||
BiS(
|
||||
data.get("weapon").flatten,
|
||||
data.get("head").flatten,
|
||||
data.get("body").flatten,
|
||||
data.get("hands").flatten,
|
||||
data.get("waist").flatten,
|
||||
data.get("legs").flatten,
|
||||
data.get("feet").flatten,
|
||||
data.get("ears").flatten,
|
||||
data.get("neck").flatten,
|
||||
data.get("wrist").flatten,
|
||||
data.get("left ring").flatten,
|
||||
data.get("right ring").flatten)
|
||||
|
||||
def apply(): BiS = BiS(Seq.empty)
|
||||
|
||||
def apply(pieces: Seq[Piece]): BiS =
|
||||
BiS(pieces.map(piece => piece.piece -> Some(piece)).toMap)
|
||||
def empty: BiS = BiS(Seq.empty)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
package me.arcanis.ffxivbis.models
|
||||
|
||||
object Job {
|
||||
|
||||
sealed trait RightSide
|
||||
object AccessoriesDex extends RightSide
|
||||
object AccessoriesInt extends RightSide
|
||||
@ -26,6 +27,7 @@ object Job {
|
||||
object BodyRanges extends LeftSide
|
||||
|
||||
sealed trait Job {
|
||||
|
||||
def leftSide: LeftSide
|
||||
def rightSide: RightSide
|
||||
|
||||
|
@ -11,5 +11,6 @@ package me.arcanis.ffxivbis.models
|
||||
import java.time.Instant
|
||||
|
||||
case class Loot(playerId: Long, piece: Piece, timestamp: Instant, isFreeLoot: Boolean) {
|
||||
|
||||
def isFreeLootToString: String = if (isFreeLoot) "yes" else "no"
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ case class Party(partyDescription: PartyDescription, rules: Seq[String], players
|
||||
}
|
||||
|
||||
object Party {
|
||||
|
||||
def apply(party: PartyDescription, config: Config,
|
||||
players: Map[Long, Player], bis: Seq[Loot], loot: Seq[Loot]): Party = {
|
||||
val bisByPlayer = bis.groupBy(_.playerId).view.mapValues(piece => BiS(piece.map(_.piece)))
|
||||
|
@ -9,9 +9,11 @@
|
||||
package me.arcanis.ffxivbis.models
|
||||
|
||||
case class PartyDescription(partyId: String, partyAlias: Option[String]) {
|
||||
|
||||
def alias: String = partyAlias.getOrElse(partyId)
|
||||
}
|
||||
|
||||
object PartyDescription {
|
||||
|
||||
def empty(partyId: String): PartyDescription = PartyDescription(partyId, None)
|
||||
}
|
@ -8,7 +8,8 @@
|
||||
*/
|
||||
package me.arcanis.ffxivbis.models
|
||||
|
||||
sealed trait Piece {
|
||||
sealed trait Piece extends Equals {
|
||||
|
||||
def pieceType: PieceType.PieceType
|
||||
def job: Job.Job
|
||||
def piece: String
|
||||
@ -22,6 +23,9 @@ sealed trait Piece {
|
||||
case _: PieceBody => Some(BodyUpgrade)
|
||||
case _: PieceWeapon => Some(WeaponUpgrade)
|
||||
}
|
||||
|
||||
// used for ring comparison
|
||||
def strictEqual(obj: Any): Boolean = equals(obj)
|
||||
}
|
||||
|
||||
trait PieceAccessory extends Piece
|
||||
@ -78,10 +82,16 @@ case class Wrist(override val pieceType: PieceType.PieceType, override val job:
|
||||
case class Ring(override val pieceType: PieceType.PieceType, override val job: Job.Job, override val piece: String = "ring")
|
||||
extends PieceAccessory {
|
||||
def withJob(other: Job.Job): Piece = copy(job = other)
|
||||
|
||||
override def equals(obj: Any): Boolean = obj match {
|
||||
case Ring(thatPieceType, thatJob, _) => (thatPieceType == pieceType) && (thatJob == job)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
override def strictEqual(obj: Any): Boolean = obj match {
|
||||
case ring: Ring => equals(obj) && (ring.piece == this.piece)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
case object AccessoryUpgrade extends PieceUpgrade {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package me.arcanis.ffxivbis.models
|
||||
|
||||
object PieceType {
|
||||
|
||||
sealed trait PieceType
|
||||
|
||||
case object Crafted extends PieceType
|
||||
|
@ -12,6 +12,7 @@ import scala.util.Try
|
||||
import scala.util.matching.Regex
|
||||
|
||||
trait PlayerIdBase {
|
||||
|
||||
def job: Job.Job
|
||||
def nick: String
|
||||
|
||||
@ -21,6 +22,7 @@ trait PlayerIdBase {
|
||||
case class PlayerId(partyId: String, job: Job.Job, nick: String) extends PlayerIdBase
|
||||
|
||||
object PlayerId {
|
||||
|
||||
def apply(partyId: String, maybeNick: Option[String], maybeJob: Option[String]): Option[PlayerId] =
|
||||
(maybeNick, maybeJob) match {
|
||||
case (Some(nick), Some(job)) => Try(PlayerId(partyId, Job.withName(job), nick)).toOption
|
||||
|
@ -38,6 +38,7 @@ case class PlayerIdWithCounters(partyId: String,
|
||||
}
|
||||
|
||||
object PlayerIdWithCounters {
|
||||
|
||||
private case class PlayerCountersComparator(values: Int*) {
|
||||
def >(that: PlayerCountersComparator): Boolean = {
|
||||
@scala.annotation.tailrec
|
||||
|
@ -29,6 +29,10 @@ trait DatabaseBiSHandler { this: Database =>
|
||||
case RemovePieceFromBiS(playerId, piece) =>
|
||||
val client = sender()
|
||||
profile.deletePieceBiS(playerId, piece).pipeTo(client)
|
||||
|
||||
case RemovePiecesFromBiS(playerId) =>
|
||||
val client = sender()
|
||||
profile.deletePiecesBiS(playerId).pipeTo(client)
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,4 +44,7 @@ object DatabaseBiSHandler {
|
||||
case class RemovePieceFromBiS(playerId: PlayerId, piece: Piece) extends Database.DatabaseRequest {
|
||||
override def partyId: String = playerId.partyId
|
||||
}
|
||||
case class RemovePiecesFromBiS(playerId: PlayerId) extends Database.DatabaseRequest {
|
||||
override def partyId: String = playerId.partyId
|
||||
}
|
||||
}
|
||||
|
@ -46,14 +46,21 @@ trait BiSProfile { this: DatabaseProfile =>
|
||||
|
||||
def deletePieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
||||
db.run(pieceBiS(BiSRep.fromPiece(playerId, piece)).delete)
|
||||
def deletePiecesBiSById(playerId: Long): Future[Int] =
|
||||
db.run(piecesBiS(Seq(playerId)).delete)
|
||||
def getPiecesBiSById(playerId: Long): Future[Seq[Loot]] = getPiecesBiSById(Seq(playerId))
|
||||
def getPiecesBiSById(playerIds: Seq[Long]): Future[Seq[Loot]] =
|
||||
db.run(piecesBiS(playerIds).result).map(_.map(_.toLoot))
|
||||
def insertPieceBiSById(piece: Piece)(playerId: Long): Future[Int] =
|
||||
db.run(bisTable.insertOrUpdate(BiSRep.fromPiece(playerId, piece)))
|
||||
getPiecesBiSById(playerId).flatMap {
|
||||
case pieces if pieces.exists(loot => loot.piece.strictEqual(piece)) => Future.successful(0)
|
||||
case _ => db.run(bisTable.insertOrUpdate(BiSRep.fromPiece(playerId, piece)))
|
||||
}
|
||||
|
||||
private def pieceBiS(piece: BiSRep) =
|
||||
piecesBiS(Seq(piece.playerId)).filter(_.piece === piece.piece)
|
||||
piecesBiS(Seq(piece.playerId)).filter { stored =>
|
||||
(stored.piece === piece.piece) && (stored.pieceType === piece.pieceType)
|
||||
}
|
||||
private def piecesBiS(playerIds: Seq[Long]) =
|
||||
bisTable.filter(_.playerId.inSet(playerIds.toSet))
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
|
||||
// generic bis api
|
||||
def deletePieceBiS(playerId: PlayerId, piece: Piece): Future[Int] =
|
||||
byPlayerId(playerId, deletePieceBiSById(piece))
|
||||
def deletePiecesBiS(playerId: PlayerId): Future[Int] =
|
||||
byPlayerId(playerId, deletePiecesBiSById)
|
||||
def getPiecesBiS(playerId: PlayerId): Future[Seq[Loot]] =
|
||||
byPlayerId(playerId, getPiecesBiSById)
|
||||
def getPiecesBiS(partyId: String): Future[Seq[Loot]] =
|
||||
@ -57,7 +59,7 @@ class DatabaseProfile(context: ExecutionContext, config: Config)
|
||||
byPlayerId(playerId, insertPieceById(loot))
|
||||
|
||||
private def byPartyId[T](partyId: String, callback: Seq[Long] => Future[T]): Future[T] =
|
||||
getPlayers(partyId).map(callback).flatten
|
||||
getPlayers(partyId).flatMap(callback)
|
||||
private def byPlayerId[T](playerId: PlayerId, callback: Long => Future[T]): Future[T] =
|
||||
getPlayer(playerId).flatMap {
|
||||
case Some(id) => callback(id)
|
||||
|
@ -19,7 +19,7 @@ trait PlayersProfile { this: DatabaseProfile =>
|
||||
nick: String, job: String, link: Option[String], priority: Int) {
|
||||
def toPlayer: Player =
|
||||
Player(playerId.getOrElse(-1), partyId, Job.withName(job), nick,
|
||||
BiS(Seq.empty), List.empty, link, priority)
|
||||
BiS.empty, Seq.empty, link, priority)
|
||||
}
|
||||
object PlayerRep {
|
||||
def fromPlayer(player: Player, id: Option[Long]): PlayerRep =
|
||||
|
Reference in New Issue
Block a user