Skip to content

Commit

Permalink
Imports now work correctly within the creator
Browse files Browse the repository at this point in the history
  • Loading branch information
hobnob committed Sep 15, 2023
1 parent 7f2c4ea commit 880eca0
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ trait GloomhavenMsg
enum GeneralMsgType extends GloomhavenMsg:
case ShowContextMenu(position: Point, menu: Menu)
case CloseContextMenu
case ShowImportDialog(msg: (String, String, String) => GloomhavenMsg)

enum CreatorMsgType extends GloomhavenMsg:
case ChangeMonsterLevel(pos: Point, m: MonsterType, playerNum: Byte, monsterLevel: MonsterLevel)
Expand All @@ -23,13 +24,11 @@ enum CreatorMsgType extends GloomhavenMsg:
case RotateRoom(r: RoomType)
case RemoveRoom(r: RoomType)
case CreateNewScenario
case ShowImportDialog
case ImportFileSelected(file: dom.File)
case ChangeScenarioTitle(title: String)
case UpdateDragMenu(sections: Batch[DragDropSection])
case SetSelectedDragSection(sectionName: String)
case NewDragStart(dragItem: RoomType | BoardOverlayType | MonsterType)
case DragEnd
case SelectFile
case ImportFile(name: String, path: String, data: String)
case ExportFile
case ExportFileString(title: String, json: String)
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ enum RoomType(val baseGame: BaseGame, val mapRef: String, val offset: Point, val
),
Size(432, 244)
)

/*
{
tileset: "gloomhaven",
Expand Down Expand Up @@ -71,3 +70,11 @@ enum RoomType(val baseGame: BaseGame, val mapRef: String, val offset: Point, val
]
}
*/

object RoomType:
def fromRef(baseGame: BaseGame, ref: String): Option[RoomType] =
RoomType.values
.find(r =>
r.baseGame == baseGame &&
r.mapRef.toLowerCase() == ref.toLowerCase()
)
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ object CreatorModel:
Batch.empty
)

def apply(scenarioTitle: String, baseGame: BaseGame): CreatorModel =
CreatorModel(
scenarioTitle,
baseGame,
Batch.empty,
Batch.empty,
Batch.empty
)

def fromJson(json: String): Either[String, CreatorModel] =
decode[CreatorStorage](json) match {
case Right(value) => value.toModel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import vgb.game.models.BoardOverlay
import vgb.game.models.ScenarioMonster
import vgb.common.Door
import vgb.common.Corridor
import vgb.common.BaseGame

final case class DoorStorage(
subType: String,
Expand All @@ -18,7 +19,27 @@ final case class DoorStorage(
room2X: Int,
room2Y: Int,
mapTileData: MapTileDataStorage
)
):
def toModel(baseGame: BaseGame) =
OverlayStorage(
this.toOverlayStorageRef(),
0,
this.direction,
List(List(this.room1X, this.room1Y))
).toModel(baseGame)

def toOverlayStorageRef() =
OverlayStorageRef(
"door",
Some(subType),
material,
size,
Some(List(mapTileData.ref)),
None,
None,
None,
None
)

object DoorStorage:
def fromRooms(
Expand All @@ -44,28 +65,6 @@ object DoorStorage:
}
)

IndigoLogger.consoleLog("door?")
IndigoLogger.consoleLog(
doorsForRoom
.flatMap(d =>
d.linkedRooms match {
case Some(links) =>
Batch.fromArray(
links
.filter(l => room.roomType != l)
.toArray
.flatMap(l =>
allRooms
.find(r => r.roomType == l)
.map(r => (d, r))
)
)
case None => Batch.empty
}
)
.toString()
)

doorsForRoom
.flatMap(d =>
d.linkedRooms match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ implicit val encodeMapTileDataStorage: Encoder[MapTileDataStorage] = new Encoder
)
}

implicit val decodeMapTileDataStorage: Decoder[MapTileDataStorage] = new Decoder[MapTileDataStorage] {
final def apply(c: HCursor): Decoder.Result[MapTileDataStorage] =
for {
ref <- c.downField("ref").as[String]
doors <- c.downField("doors").as[List[DoorStorage]]
overlays <- c.downField("overlays").as[List[OverlayStorage]]
monsters <- c.downField("monsters").as[List[ScenarioMonsterStorage]]
turns <- c.downField("turns").as[Byte]
} yield new MapTileDataStorage(ref, doors, overlays, monsters, turns)
}

object MapTileDataStorage:
def apply(
room: Room,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ final case class OverlayStorage(
.foldLeft(Option.empty[Point])((p, c) => p.map(c.min).orElse(Some(c)))
.getOrElse(Point.zero)

val o = overlay.copy(origin = minCell - minPoint)
IndigoLogger.consoleLog(o.worldCells.toString())
o
overlay.copy(origin = minCell - minPoint)
}

private def decodeOverlay(baseGame: BaseGame) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,12 @@ final case class RoomStorage(
rotationPoint: CellStorage
) {
def toModel(baseGame: BaseGame): Either[String, Room] =
val roomType = RoomType.values
.find(r => r.baseGame == baseGame && r.mapRef.toLowerCase() == data.ref.toLowerCase())
roomType match {
RoomType.fromRef(baseGame, data.ref) match {
case Some(roomType) =>
Right(
Room(roomType, Point(data.origin.x, data.origin.y), TileRotation.fromByte(data.turns))
)
case None => Left(s"""Map tile ${data.ref} does not exist for ${baseGame}""")

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,92 @@ import vgb.common.Corridor
import vgb.common.Hexagon
import vgb.common.Door
import vgb.common.StartingLocation
import vgb.common.BaseGame
import vgb.game.models.TileRotation

final case class ScenarioStorage(
id: Int,
title: String,
baseGame: Option[String],
mapTileData: Option[MapTileDataStorage],
angle: Float,
additionalMonsters: List[String]
) {
override def toString(): String =
this.asJson.deepDropNullValues.noSpaces

def toCreatorModel(): Either[String, CreatorModel] =
val baseGame = this.baseGame match {
case Some(value) =>
BaseGame.values.find(g => g.toString().toLowerCase() == value) match {
case Some(bg) => bg
case None => BaseGame.Gloomhaven
}
case None => BaseGame.Gloomhaven
}

val model = CreatorModel(this.title, baseGame)

this.mapTileData match {
case Some(data) => decodeMapTileData(Point.zero, Point.zero, data, model)
case None => Right(model)
}

private def decodeMapTileData(
origin: Point,
localOffset: Point,
data: MapTileDataStorage,
model: CreatorModel
): Either[String, CreatorModel] =
RoomType.fromRef(model.baseGame, data.ref) match {
case Some(roomType) =>
val room = Room(roomType, origin.moveBy(localOffset), TileRotation.fromByte(data.turns))
val overlays = data.overlays.map(o => o.toModel(model.baseGame).map(o => o.copy(origin = room.localToWorld(o.origin))))
val monsters = data.monsters.map(m => m.toModel(model.baseGame).map(m => m.copy(initialPosition = room.localToWorld(m.initialPosition))))

val errors =
overlays.collect { case Left(e) => e } ++
monsters.collect { case Left(e) => e }

errors.headOption match {
case Some(value) => Left(value)
case _ =>
val newModel = model.copy(
rooms =
if model.rooms.exists(r => r.roomType == roomType) then model.rooms
else model.rooms :+ room,
overlays = model.overlays ++ Batch.fromList(overlays.collect { case Right(o) => o }),
monsters = model.monsters ++ Batch.fromList(monsters.collect { case Right(m) => m })
)

decodeDoorData(room, data.doors, newModel)
}

case None => Left(s"""Map tile ${data.ref} does not exist for ${baseGame}""")
}

private def decodeDoorData(room: Room, doors: List[DoorStorage], model: CreatorModel): Either[String, CreatorModel] =
doors
.foldLeft(Right(model).asInstanceOf[Either[String, CreatorModel]])((m, d) =>
m match {
case Right(newModel) =>
val door = d
.toModel(model.baseGame)
.map(d1 => d1.copy(origin = room.localToWorld(Point(d.room1X, d.room1Y))))

door match {
case Right(d2) =>
decodeMapTileData(
d2.origin,
Point(d.room2X, d.room2Y),
d.mapTileData,
newModel.copy(overlays = newModel.overlays :+ d2)
)
case Left(d2) => Left(d2)
}
case Left(_) => m
}
)
}

object ScenarioStorage:
Expand Down Expand Up @@ -51,6 +127,7 @@ object ScenarioStorage:
case head :: tail =>
ScenarioStorage(
model.scenarioTitle,
model.baseGame,
head,
overlays,
monsters,
Expand All @@ -60,6 +137,10 @@ object ScenarioStorage:
ScenarioStorage(
0,
model.scenarioTitle,
model.baseGame match {
case BaseGame.Gloomhaven => None
case _ => Some(model.baseGame.toString().toLowerCase())
},
None,
0,
List.empty
Expand All @@ -68,6 +149,7 @@ object ScenarioStorage:

def apply(
title: String,
baseGame: BaseGame,
startingRoom: Room,
overlays: Batch[(RoomType, BoardOverlay)],
monsters: Batch[(RoomType, ScenarioMonster)],
Expand All @@ -76,6 +158,10 @@ object ScenarioStorage:
ScenarioStorage(
0,
title,
baseGame match {
case BaseGame.Gloomhaven => None
case _ => Some(baseGame.toString().toLowerCase())
},
Some(
MapTileDataStorage(
startingRoom,
Expand All @@ -89,6 +175,12 @@ object ScenarioStorage:
List.empty
)

def fromJson(json: String): Either[String, ScenarioStorage] =
decode[ScenarioStorage](json) match {
case Right(value) => Right(value)
case Left(err) => Left(err.getMessage())
}

private def assignOverlaysToRoom(
overlays: Batch[BoardOverlay],
roomCellMap: Map[Point, Room]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final case class CreatorScene(tyrianSubSystem: TyrianSubSystem[IO, GloomhavenMsg
EventFilters.AllowAll

val subSystems: Set[SubSystem] =
Set(tyrianSubSystem)
Set()

def updateModel(
context: SceneContext[Size],
Expand Down Expand Up @@ -394,6 +394,16 @@ final case class CreatorScene(tyrianSubSystem: TyrianSubSystem[IO, GloomhavenMsg
)
)
)
case CreatorMsgType.ImportFile(_, _, data) =>
ScenarioStorage.fromJson(data) match {
case Right(scenario) =>
scenario.toCreatorModel() match {
case Right(newModel) =>
Outcome(newModel).addGlobalEvents(StorageEvent.Save(saveSlot, newModel.toString()))
case _ => Outcome(model)
}
case _ => Outcome(model)
}
case _ => Outcome(model)
}

Expand Down
22 changes: 22 additions & 0 deletions VirtualGloomhavenBoard/Indigo/vgb-ui/src/vgb/ui/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import vgb.common.{MenuItem, MenuSeparator}
import vgb.ui.models.UiModel
import vgb.ui.scenes.CreatorModel
import tyrian.cmds.Logger
import tyrian.cmds.File
import indigo.shared.IndigoLogger
import tyrian.cmds.FileReader

import org.scalajs.dom

enum Msg:
case NoOp
Expand All @@ -29,6 +34,8 @@ enum Msg:
case CloseContextItem(itemId: Byte)
case CloseContextMenu
case ToggleMainMenu
case ReadFile(file: dom.File, msg: (String, String, String) => GloomhavenMsg)
case FileRead(file: FileReader.Result[String], msg: (String, String, String) => GloomhavenMsg)

@JSExportTopLevel("TyrianApp")
object Main extends TyrianApp[Msg, Model]:
Expand Down Expand Up @@ -94,6 +101,19 @@ object Main extends TyrianApp[Msg, Model]:
model.copy(sceneModel = model.sceneModel.toggleMainMenu()),
Cmd.None
)
case Msg.ReadFile(file, msg) =>
(
model,
FileReader.readText(file)(r => Msg.FileRead(r, msg))
)
case Msg.FileRead(fileResult, msg) =>
update(model)(
fileResult match {
case _: FileReader.Result.Error[String] => Msg.NoOp
case FileReader.Result.File(name, path, data) =>
Msg.IndigoReceive(msg(name, path, data))
}
)

def view(model: Model): Html[Msg] =
val sceneModel = model.scene.getModel(model)
Expand Down Expand Up @@ -198,6 +218,8 @@ object Main extends TyrianApp[Msg, Model]:
)
case GeneralMsgType.CloseContextMenu =>
(model.copy(sceneModel = model.sceneModel.updateContextMenu(None)), Cmd.None)
case GeneralMsgType.ShowImportDialog(msg) =>
(model, File.select(Array("application/json"))(f => Msg.ReadFile(f, msg)))
}
case _ =>
val (sceneModel, cmd) = model.scene.update(msg, model.scene.getModel(model))
Expand Down
Loading

0 comments on commit 880eca0

Please sign in to comment.