~comcloudway/little_town

90d68082ba0c1f31a1c320d5e4e64a221ad47bc5 — Jakob Meier 1 year, 1 month ago aae8a16
Formatted code using cargo clippy and cargo fmt
M src/game/blocks.rs => src/game/blocks.rs +674 -233
@@ 1,14 1,10 @@
use macroquad::prelude::*;
use crate::screens::build::{
    TEXTURE_DEPTH, TEXTURE_INNER_HEIGHT, TEXTURE_WIDTH, TEXTURE_Y_WHITESPACE,
};
use crate::textures::DirectionalTexture;
use macroquad::prelude::*;
use nanoserde::{DeJson, SerJson};
use std::collections::HashMap;
use crate::screens::build::{
    TEXTURE_WIDTH,
    TEXTURE_Y_WHITESPACE,
    TEXTURE_DEPTH,
    TEXTURE_INNER_HEIGHT
};


/// A block's face
///   /'\


@@ 24,14 20,13 @@ use crate::screens::build::{
pub enum Face {
    Top,
    Left,
    Right
    Right,
}
impl Face {
    /// gets the face (if any) that was clicked
    /// coordinates have to be normalized,
    /// the top left should be (0,0)
    pub fn from_xy(x: f32, y: f32, scale: f32) -> Option<Self> {

        // test A face
        {
            let ox = (TEXTURE_WIDTH * scale / 2.0 - x).abs();


@@ 40,7 35,7 @@ impl Face {
            let y_max = TEXTURE_DEPTH * scale / 2.0;
            let x_max = TEXTURE_WIDTH * scale / 2.0;

            let grow = (0.0 - y_max)/(x_max - 0.0);
            let grow = (0.0 - y_max) / (x_max - 0.0);
            let cy = grow.mul_add(ox, y_max);

            if cy >= oy {


@@ 58,7 53,7 @@ impl Face {
            let y_max = TEXTURE_DEPTH * scale / 2.0;
            let x_max = TEXTURE_WIDTH * scale / 2.0;

            let grow = (0.0 - y_max)/(x_max - 0.0);
            let grow = (0.0 - y_max) / (x_max - 0.0);

            let y_top = grow.mul_add(ox_abs, y_max);
            let y_bottom = TEXTURE_INNER_HEIGHT.mul_add(scale, grow.mul_add(ox_abs, y_max));


@@ 100,7 95,7 @@ pub enum Category {
    /// i.e bridges
    Wood,
    /// all items
    All
    All,
}

/// rust macro to autogenerate the Block enum


@@ 168,225 163,671 @@ macro_rules! auto_gen_blocks {
}

auto_gen_blocks!(
    ( BalconyStone, "exp/Tiles/balcony_stone", [Building] ),
    ( BalconyWood, "exp/Tiles/balcony_wood", [Building] ),
    ( Bridge, "base/Tiles/bridge", [Wood] ),
    ( BuildingCenter, "base/Tiles/building_center", [Building] ),
    ( BuildingCenterBeige, "base/Tiles/building_centerBeige", [Building] ),
    ( BuildingCorner, "base/Tiles/building_corner", [Building] ),
    ( BuildingCornerBeige, "base/Tiles/building_cornerBeige", [Building] ),
    ( BuildingDoor, "base/Tiles/building_door", [Building] ),
    ( BuildingDoorBeige, "base/Tiles/building_doorBeige", [Building] ),
    ( BuildingDoorWindows, "base/Tiles/building_doorWindows", [Building] ),
    ( BuildingDoorWindowsBeige, "base/Tiles/building_doorWindowsBeige", [Building] ),
    ( BuildingWindow, "base/Tiles/building_window", [Building] ),
    ( BuildingWindowBeige, "base/Tiles/building_windowBeige", [Building] ),
    ( BuildingWindows, "base/Tiles/building_windows", [Building] ),
    ( BuildingWindowsBeige, "base/Tiles/building_windowsBeige", [Building] ),
    ( BuildingStack, "exp/Tiles/building_stack", [Building] ),
    ( BuildingStackBeige, "exp/Tiles/building_stackBeige", [Building] ),
    ( BuildingStackCorner, "exp/Tiles/building_stackCorner", [Building] ),
    ( BuildingStackCornerBeige, "exp/Tiles/building_stackCornerBeige", [Building] ),

    ( DesertBuilding, "desert/Tiles/building_center", [Building, Desert] ),
    ( DesertBuildingSides, "desert/Tiles/building_sides", [Building, Desert] ),
    ( DesertDarkBuilding, "desert/Tiles/building_dark_center", [Building, Desert] ),
    ( DesertDarkBuildingDoor, "desert/Tiles/building_dark_center_door", [Building, Desert] ),
    ( DesertDarkBuildingWindows, "desert/Tiles/building_dark_center_windows", [Building, Desert] ),
    ( DesertDarkBuildingSides, "desert/Tiles/building_dark_sides", [Building, Desert] ),
    ( DesertDarkBuildingSidesWindows, "desert/Tiles/building_dark_sides_windows", [Building, Desert] ),

    ( CastleBend, "base/Tiles/castle_bend", [Building] ),
    ( CastleCenter, "base/Tiles/castle_center", [Building] ),
    ( CastleCorner, "base/Tiles/castle_corner", [Building] ),
    ( CastleEnd, "exp/Tiles/castle_end", [Building] ),
    ( CastleEndRound, "exp/Tiles/castle_endRound", [Building] ),
    ( CastleEndRuined, "exp/Tiles/castle_endRuined", [Building] ),
    ( CastleGate, "base/Tiles/castle_gate", [Building] ),
    ( CastleGateOpen, "base/Tiles/castle_gateOpen", [Building] ),
    ( CastleSlope, "base/Tiles/castle_slope", [Building] ),
    ( CastleTower, "base/Tiles/castle_tower", [Building] ),
    ( CastleTowerCenter, "exp/Tiles/castle_towerCenter", [Building] ),
    ( CastleTowerBeige, "base/Tiles/castle_towerBeige", [Building] ),
    ( CastleTowerBeigeBase, "exp/Tiles/castle_towerBeigeBase", [Building] ),
    ( CastleTowerBeigeTop, "exp/Tiles/castle_towerBeigeTop", [Building, Roof] ),
    ( CastleTowerBrown, "base/Tiles/castle_towerBrown", [Building] ),
    ( CastleTowerBrownBase, "exp/Tiles/castle_towerBrownBase", [Building] ),
    ( CastleTowerBrownTop, "exp/Tiles/castle_towerBrownTop", [Building, Roof] ),
    ( CastleTowerGreen, "base/Tiles/castle_towerGreen", [Building] ),
    ( CastleTowerGreenBase, "exp/Tiles/castle_towerGreenBase", [Building] ),
    ( CastleTowerGreenTop, "exp/Tiles/castle_towerGreenTop", [Building, Roof] ),
    ( CastleTowerPurple, "base/Tiles/castle_towerPurple", [Building] ),
    ( CastleTowerPurpleBase, "exp/Tiles/castle_towerPurpleBase", [Building] ),
    ( CastleTowerPurpleTop, "exp/Tiles/castle_towerPurpleTop", [Building, Roof] ),
    ( CastleWall, "base/Tiles/castle_wall", [Building] ),
    ( CastleWindow, "base/Tiles/castle_window", [Building] ),

    ( Cliff, "exp/Tiles/cliff", [Block] ),
    ( CliffCorner, "exp/Tiles/cliff_corner", [Block] ),
    ( CliffCornerInner, "exp/Tiles/cliff_cornerInner", [Block] ),
    ( CliffEntrance, "exp/Tiles/cliff_entrance", [Block] ),
    ( CliffTop, "exp/Tiles/cliff_top", [Block] ),
    ( CliffTopEntrance, "exp/Tiles/cliff_topEntrance", [Block] ),
    ( CliffTopCorner, "exp/Tiles/cliff_topCorner", [Block] ),
    ( CliffTopCornerInner, "exp/Tiles/cliff_topCornerInner", [Block] ),

    ( DirtCenter, "base/Tiles/dirt_center", [Block] ),
    ( DirtCorner, "exp/Tiles/dirt_corner", [Block] ),
    ( DirtLow, "base/Tiles/dirt_low", [Block] ),

    ( Fence, "exp/Tiles/fence_wood", [Wood] ),
    ( FenceCorner, "exp/Tiles/fence_woodCorner", [Wood] ),
    ( FenceCurve, "exp/Tiles/fence_woodCurve", [Wood] ),
    ( FenceEnd, "exp/Tiles/fence_woodEnd", [Wood] ),
    ( FenceDouble, "exp/Tiles/fence_woodDouble", [Wood] ),
    ( FenceDoubleCorner, "exp/Tiles/fence_woodDoubleCorner", [Wood] ),
    ( FenceDoubleCurve, "exp/Tiles/fence_woodDoubleCurve", [Wood] ),
    ( FenceDoubleEnd, "exp/Tiles/fence_woodDoubleEnd", [Wood]),

    ( Furrow, "exp/Tiles/furrow", [Plant] ),
    ( FurrowCropWheat, "exp/Tiles/furrow_cropWheat", [Plant] ),
    ( FurrowCrop, "exp/Tiles/furrow_crop", [Plant] ),
    ( FurrowEnd, "exp/Tiles/furrow_end", [Plant] ),

    ( Grass, "exp/Tiles/grass_block", [Block] ),
    ( GrassCenter, "base/Tiles/grass_center", [Block] ),
    ( GrassCorner, "base/Tiles/grass_corner", [Block] ),
    ( GrassSlope, "base/Tiles/grass_slope", [Block] ),
    ( GrassSlopeConcave, "base/Tiles/grass_slopeConcave", [Block] ),
    ( GrassSlopeConvex, "base/Tiles/grass_slopeConvex", [Block] ),
    ( GrassPath, "base/Tiles/grass_path", [Block] ),
    ( GrassPathBend, "base/Tiles/grass_pathBend", [Block] ),
    ( GrassPathCorner, "base/Tiles/grass_pathCorner", [Block] ),
    ( GrassPathCrossing, "base/Tiles/grass_pathCrossing", [Block] ),
    ( GrassPathEnd, "base/Tiles/grass_pathEnd", [Block] ),
    ( GrassPathEndSquare, "base/Tiles/grass_pathEndSquare", [Block] ),
    ( GrassPathSlope, "base/Tiles/grass_pathSlope", [Block] ),
    ( GrassPathSplit, "base/Tiles/grass_pathSplit", [Block] ),
    ( GrassRiver, "base/Tiles/grass_river", [Block, Water] ),
    ( GrassRiverBend, "base/Tiles/grass_riverBend", [Block, Water] ),
    ( GrassRiverBridge, "base/Tiles/grass_riverBridge", [Block, Water] ),
    ( GrassRiverCorner, "base/Tiles/grass_riverCorner", [Block, Water] ),
    ( GrassRiverCrossing, "base/Tiles/grass_riverCrossing", [Block, Water] ),
    ( GrassRiverEnd, "base/Tiles/grass_riverEnd", [Block, Water] ),
    ( GrassRiverEndSquare, "base/Tiles/grass_riverEndSquare", [Block, Water] ),
    ( GrassRiverSlope, "base/Tiles/grass_riverSlope", [Block, Water] ),
    ( GrassRiverSplit, "base/Tiles/grass_riverSplit", [Block, Water] ),
    ( GrassWater, "base/Tiles/grass_water", [Block, Water] ),
    ( GrassWaterConcave, "base/Tiles/grass_waterConcave", [Block, Water] ),
    ( GrassWaterConvex, "base/Tiles/grass_waterConvex", [Block, Water] ),
    ( GrassWaterRiver, "base/Tiles/grass_waterRiver", [Block, Water] ),

    ( DesertGrass, "desert/Tiles/grass_center", [Block, Desert] ),
    ( DesertGrassSlope, "desert/Tiles/grass_slope", [Block, Desert] ),
    ( DesertGrassSlopeConcave, "desert/Tiles/grass_slopeConcave", [Block, Desert] ),
    ( DesertGrassSlopeConvex, "desert/Tiles/grass_slopeConvex", [Block, Desert] ),
    ( DesertGrassCorner, "desert/Tiles/grass_corner", [Block, Desert] ),
    ( DesertGrassPath, "desert/Tiles/grass_path", [Block, Desert] ),
    ( DesertGrassPathBend, "desert/Tiles/grass_pathBend", [Block, Desert] ),
    ( DesertGrassPathCorner, "desert/Tiles/grass_pathCorner", [Block, Desert] ),
    ( DesertGrassPathCrossing, "desert/Tiles/grass_pathCrossing", [Block, Desert] ),
    ( DesertGrassPathEnd, "desert/Tiles/grass_pathEnd", [Block, Desert] ),
    ( DesertGrassPathEndSquare, "desert/Tiles/grass_pathEndSquare", [Block, Desert] ),
    ( DesertGrassPathSlope, "desert/Tiles/grass_pathSlope", [Block, Desert] ),
    ( DesertGrassPathSplit, "desert/Tiles/grass_pathSplit", [Block, Desert] ),
    ( DesertGrassRiver, "desert/Tiles/grass_river", [Block, Desert, Water] ),
    ( DesertGrassRiverBend, "desert/Tiles/grass_riverBend", [Block, Desert, Water] ),
    ( DesertGrassRiverBridge, "desert/Tiles/grass_riverBridge", [Block, Desert, Water] ),
    ( DesertGrassRiverCorner, "desert/Tiles/grass_riverCorner", [Block, Desert, Water] ),
    ( DesertGrassRiverCrossing, "desert/Tiles/grass_riverCrossing", [Block, Desert, Water] ),
    ( DesertGrassRiverEnd, "desert/Tiles/grass_riverEnd", [Block, Desert, Water] ),
    ( DesertGrassRiverEndSquare, "desert/Tiles/grass_riverEndSquare", [Block, Desert, Water] ),
    ( DesertGrassRiverSlope, "desert/Tiles/grass_riverSlope", [Block, Desert, Water] ),
    ( DesertGrassRiverSplit, "desert/Tiles/grass_riverSplit", [Block, Desert, Water] ),
    ( DesertGrassWater, "desert/Tiles/grass_water", [Block, Desert, Water] ),
    ( DesertGrassWaterConcave, "desert/Tiles/grass_waterConcave", [Block, Desert, Water] ),
    ( DesertGrassWaterConvex, "desert/Tiles/grass_waterConvex", [Block, Desert, Water] ),
    ( DesertGrassWaterRiver, "desert/Tiles/grass_waterRiver", [Block, Desert, Water] ),

    ( RocksDirt, "base/Tiles/rocks_dirt", [Plant] ),
    ( RocksGrass, "base/Tiles/rocks_grass", [Plant] ),
    ( RocksDesert, "desert/Tiles/rocks", [Plant, Desert] ),

    ( DesertOverhang, "desert/Tiles/overhang", [Building, Desert] ),
    ( DesertOverhangSmall, "desert/Tiles/overhang_small", [Building, Desert] ),

    ( RoofChurchBeige, "base/Tiles/roof_churchBeige", [Building, Roof] ),
    ( RoofChuchBrown, "base/Tiles/roof_churchBrown", [Building, Roof] ),
    ( RoofChurchGreen, "base/Tiles/roof_churchGreen", [Building, Roof] ),
    ( RoofChurchPurple, "base/Tiles/roof_churchPurple", [Building, Roof] ),
    ( RoofGableBeige, "base/Tiles/roof_gableBeige", [Building, Roof] ),
    ( RoofGableCornerBeige, "exp/Tiles/roof_gableCornerBeige", [Building, Roof] ),
    ( RoofGableBrown, "base/Tiles/roof_gableBrown", [Building, Roof] ),
    ( RoofGableCornerBrown, "exp/Tiles/roof_gableCornerBrown", [Building, Roof] ),
    ( RoofGableGreen, "base/Tiles/roof_gableGreen", [Building, Roof] ),
    ( RoofGableCornerGreen, "exp/Tiles/roof_gableCornerGreen", [Building, Roof] ),
    ( RoofGablePurple, "base/Tiles/roof_gablePurple", [Building, Roof] ),
    ( RoofGableCornerPurple, "exp/Tiles/roof_gableCornerPurple", [Building, Roof] ),
    ( RoofPointBeige, "base/Tiles/roof_pointBeige", [Building, Roof] ),
    ( RoofPointBrown, "base/Tiles/roof_pointBrown", [Building, Roof] ),
    ( RoofPointGreen, "base/Tiles/roof_pointGreen", [Building, Roof] ),
    ( RoofPointPurple, "base/Tiles/roof_pointPurple", [Building, Roof] ),
    ( RoofRoundBeige, "base/Tiles/roof_roundBeige", [Building, Roof] ),
    ( RoofRoundCornerBeige, "exp/Tiles/roof_roundCornerBeige", [Building, Roof] ),
    ( RoofRoundBrown, "base/Tiles/roof_roundBrown", [Building, Roof] ),
    ( RoofRoundCornerBrown, "exp/Tiles/roof_roundCornerBrown", [Building, Roof] ),
    ( RoofRoundGreen, "base/Tiles/roof_roundGreen", [Building, Roof] ),
    ( RoofRoundCornerGreen, "exp/Tiles/roof_roundCornerGreen", [Building, Roof] ),
    ( RoofRoundPurple, "base/Tiles/roof_roundPurple", [Building, Roof] ),
    ( RoofRoundCornerPurple, "exp/Tiles/roof_roundCornerPurple", [Building, Roof] ),
    ( RoofRoundedBeige, "base/Tiles/roof_roundedBeige", [Building, Roof] ),
    ( RoofRoundedBrown, "base/Tiles/roof_roundedBrown", [Building, Roof] ),
    ( RoofRoundedGreen, "base/Tiles/roof_roundedGreen", [Building, Roof] ),
    ( RoofRoundedPurple, "base/Tiles/roof_roundedPurple", [Building, Roof] ),
    ( RoofSlantBeige, "base/Tiles/roof_slantBeige", [Building, Roof] ),
    ( RoofSlantCornerBeige, "exp/Tiles/roof_slantCornerBeige", [Building, Roof] ),
    ( RoofSlantCornerInnerBeige, "exp/Tiles/roof_slantCornerInnerBeige", [Building, Roof] ),
    ( RoofSlantBrown, "base/Tiles/roof_slantBrown", [Building, Roof] ),
    ( RoofSlantCornerBrown, "exp/Tiles/roof_slantCornerBrown", [Building, Roof] ),
    ( RoofSlantCornerInnerBrown, "exp/Tiles/roof_slantCornerInnerBrown", [Building, Roof] ),
    ( RoofSlantGreen, "base/Tiles/roof_slantGreen", [Building, Roof] ),
    ( RoofSlantCornerGreen, "exp/Tiles/roof_slantCornerGreen", [Building, Roof] ),
    ( RoofSlantCornerInnerGreen, "exp/Tiles/roof_slantCornerInnerGreen", [Building, Roof] ),
    ( RoofSlantPurple, "base/Tiles/roof_slantPurple", [Building, Roof] ),
    ( RoofSlantCornerPurple, "exp/Tiles/roof_slantCornerPurple", [Building, Roof] ),
    ( RoofSlantCornerInnerPurple, "exp/Tiles/roof_slantCornerInnerPurple", [Building, Roof] ),

    ( DesertDome, "desert/Tiles/dome", [Building, Desert, Roof] ),
    ( DesertDomeSmall, "desert/Tiles/dome_small", [Building, Desert, Roof] ),

    ( DesertStairs, "desert/Tiles/stairs_full", [Building, Desert] ),
    ( DesertStairsLeft, "desert/Tiles/stairs_left", [Building, Desert] ),
    ( DesertStairsRight, "desert/Tiles/stairs_right", [Building, Desert] ),

    ( StructureArch, "base/Tiles/structure_arch", [Wood] ),
    ( StructureHigh, "base/Tiles/structure_high", [Wood] ),
    ( StructureLow, "base/Tiles/structure_low", [Wood] ),
    ( StructureTent, "desert/Tiles/structure_tent", [Building, Wood, Desert] ),
    ( StructureTentSlant, "desert/Tiles/structure_tentSlant", [Building, Wood, Desert] ),

    ( DesertTiles, "desert/Tiles/tiles", [Block, Desert] ),
    ( DesertTilesCrumbled, "desert/Tiles/tiles_crumbled", [Block, Desert] ),
    ( DesertTilesDecorated, "desert/Tiles/tiles_decorated", [Block, Desert] ),
    ( DesertTilesSteps, "desert/Tiles/tiles_steps", [Block, Desert] ),

    ( Trees, "base/Tiles/tree_multiple", [Plant] ),
    ( Tree, "base/Tiles/tree_single", [Plant] ),
    ( Pine, "exp/Tiles/tree_pine", [Plant] ),
    ( PineLarge, "exp/Tiles/tree_pineLarge", [Plant] ),
    ( PalmTree, "desert/Tiles/tree", [Plant, Desert] ),
    ( PalmTrees, "desert/Tiles/trees", [Plant, Desert] ),

    ( DesertWalls, "desert/Tiles/walls_square", [Desert, Building] ),
    ( DesertWallsBroken, "desert/Tiles/walls_broken", [Desert, Building] ),
    ( DesertWallsCorner, "desert/Tiles/walls_corner", [Desert, Building] ),
    ( DesertWallsEnd, "desert/Tiles/walls_end", [Desert, Building] ),
    ( DesertWallsLeft, "desert/Tiles/walls_left", [Desert, Building] ),
    ( DesertWallsRight, "desert/Tiles/walls_right", [Desert, Building] ),

    ( WaterCenter, "base/Tiles/water_center", [Block, Water] ),
    ( WaterFall, "base/Tiles/water_fall", [Block, Water] ),
    ( Well, "exp/Tiles/well", [Water] )
    (BalconyStone, "exp/Tiles/balcony_stone", [Building]),
    (BalconyWood, "exp/Tiles/balcony_wood", [Building]),
    (Bridge, "base/Tiles/bridge", [Wood]),
    (BuildingCenter, "base/Tiles/building_center", [Building]),
    (
        BuildingCenterBeige,
        "base/Tiles/building_centerBeige",
        [Building]
    ),
    (BuildingCorner, "base/Tiles/building_corner", [Building]),
    (
        BuildingCornerBeige,
        "base/Tiles/building_cornerBeige",
        [Building]
    ),
    (BuildingDoor, "base/Tiles/building_door", [Building]),
    (
        BuildingDoorBeige,
        "base/Tiles/building_doorBeige",
        [Building]
    ),
    (
        BuildingDoorWindows,
        "base/Tiles/building_doorWindows",
        [Building]
    ),
    (
        BuildingDoorWindowsBeige,
        "base/Tiles/building_doorWindowsBeige",
        [Building]
    ),
    (BuildingWindow, "base/Tiles/building_window", [Building]),
    (
        BuildingWindowBeige,
        "base/Tiles/building_windowBeige",
        [Building]
    ),
    (BuildingWindows, "base/Tiles/building_windows", [Building]),
    (
        BuildingWindowsBeige,
        "base/Tiles/building_windowsBeige",
        [Building]
    ),
    (BuildingStack, "exp/Tiles/building_stack", [Building]),
    (
        BuildingStackBeige,
        "exp/Tiles/building_stackBeige",
        [Building]
    ),
    (
        BuildingStackCorner,
        "exp/Tiles/building_stackCorner",
        [Building]
    ),
    (
        BuildingStackCornerBeige,
        "exp/Tiles/building_stackCornerBeige",
        [Building]
    ),
    (
        DesertBuilding,
        "desert/Tiles/building_center",
        [Building, Desert]
    ),
    (
        DesertBuildingSides,
        "desert/Tiles/building_sides",
        [Building, Desert]
    ),
    (
        DesertDarkBuilding,
        "desert/Tiles/building_dark_center",
        [Building, Desert]
    ),
    (
        DesertDarkBuildingDoor,
        "desert/Tiles/building_dark_center_door",
        [Building, Desert]
    ),
    (
        DesertDarkBuildingWindows,
        "desert/Tiles/building_dark_center_windows",
        [Building, Desert]
    ),
    (
        DesertDarkBuildingSides,
        "desert/Tiles/building_dark_sides",
        [Building, Desert]
    ),
    (
        DesertDarkBuildingSidesWindows,
        "desert/Tiles/building_dark_sides_windows",
        [Building, Desert]
    ),
    (CastleBend, "base/Tiles/castle_bend", [Building]),
    (CastleCenter, "base/Tiles/castle_center", [Building]),
    (CastleCorner, "base/Tiles/castle_corner", [Building]),
    (CastleEnd, "exp/Tiles/castle_end", [Building]),
    (CastleEndRound, "exp/Tiles/castle_endRound", [Building]),
    (CastleEndRuined, "exp/Tiles/castle_endRuined", [Building]),
    (CastleGate, "base/Tiles/castle_gate", [Building]),
    (CastleGateOpen, "base/Tiles/castle_gateOpen", [Building]),
    (CastleSlope, "base/Tiles/castle_slope", [Building]),
    (CastleTower, "base/Tiles/castle_tower", [Building]),
    (
        CastleTowerCenter,
        "exp/Tiles/castle_towerCenter",
        [Building]
    ),
    (CastleTowerBeige, "base/Tiles/castle_towerBeige", [Building]),
    (
        CastleTowerBeigeBase,
        "exp/Tiles/castle_towerBeigeBase",
        [Building]
    ),
    (
        CastleTowerBeigeTop,
        "exp/Tiles/castle_towerBeigeTop",
        [Building, Roof]
    ),
    (CastleTowerBrown, "base/Tiles/castle_towerBrown", [Building]),
    (
        CastleTowerBrownBase,
        "exp/Tiles/castle_towerBrownBase",
        [Building]
    ),
    (
        CastleTowerBrownTop,
        "exp/Tiles/castle_towerBrownTop",
        [Building, Roof]
    ),
    (CastleTowerGreen, "base/Tiles/castle_towerGreen", [Building]),
    (
        CastleTowerGreenBase,
        "exp/Tiles/castle_towerGreenBase",
        [Building]
    ),
    (
        CastleTowerGreenTop,
        "exp/Tiles/castle_towerGreenTop",
        [Building, Roof]
    ),
    (
        CastleTowerPurple,
        "base/Tiles/castle_towerPurple",
        [Building]
    ),
    (
        CastleTowerPurpleBase,
        "exp/Tiles/castle_towerPurpleBase",
        [Building]
    ),
    (
        CastleTowerPurpleTop,
        "exp/Tiles/castle_towerPurpleTop",
        [Building, Roof]
    ),
    (CastleWall, "base/Tiles/castle_wall", [Building]),
    (CastleWindow, "base/Tiles/castle_window", [Building]),
    (Cliff, "exp/Tiles/cliff", [Block]),
    (CliffCorner, "exp/Tiles/cliff_corner", [Block]),
    (CliffCornerInner, "exp/Tiles/cliff_cornerInner", [Block]),
    (CliffEntrance, "exp/Tiles/cliff_entrance", [Block]),
    (CliffTop, "exp/Tiles/cliff_top", [Block]),
    (CliffTopEntrance, "exp/Tiles/cliff_topEntrance", [Block]),
    (CliffTopCorner, "exp/Tiles/cliff_topCorner", [Block]),
    (
        CliffTopCornerInner,
        "exp/Tiles/cliff_topCornerInner",
        [Block]
    ),
    (DirtCenter, "base/Tiles/dirt_center", [Block]),
    (DirtCorner, "exp/Tiles/dirt_corner", [Block]),
    (DirtLow, "base/Tiles/dirt_low", [Block]),
    (Fence, "exp/Tiles/fence_wood", [Wood]),
    (FenceCorner, "exp/Tiles/fence_woodCorner", [Wood]),
    (FenceCurve, "exp/Tiles/fence_woodCurve", [Wood]),
    (FenceEnd, "exp/Tiles/fence_woodEnd", [Wood]),
    (FenceDouble, "exp/Tiles/fence_woodDouble", [Wood]),
    (
        FenceDoubleCorner,
        "exp/Tiles/fence_woodDoubleCorner",
        [Wood]
    ),
    (FenceDoubleCurve, "exp/Tiles/fence_woodDoubleCurve", [Wood]),
    (FenceDoubleEnd, "exp/Tiles/fence_woodDoubleEnd", [Wood]),
    (Furrow, "exp/Tiles/furrow", [Plant]),
    (FurrowCropWheat, "exp/Tiles/furrow_cropWheat", [Plant]),
    (FurrowCrop, "exp/Tiles/furrow_crop", [Plant]),
    (FurrowEnd, "exp/Tiles/furrow_end", [Plant]),
    (Grass, "exp/Tiles/grass_block", [Block]),
    (GrassCenter, "base/Tiles/grass_center", [Block]),
    (GrassCorner, "base/Tiles/grass_corner", [Block]),
    (GrassSlope, "base/Tiles/grass_slope", [Block]),
    (GrassSlopeConcave, "base/Tiles/grass_slopeConcave", [Block]),
    (GrassSlopeConvex, "base/Tiles/grass_slopeConvex", [Block]),
    (GrassPath, "base/Tiles/grass_path", [Block]),
    (GrassPathBend, "base/Tiles/grass_pathBend", [Block]),
    (GrassPathCorner, "base/Tiles/grass_pathCorner", [Block]),
    (GrassPathCrossing, "base/Tiles/grass_pathCrossing", [Block]),
    (GrassPathEnd, "base/Tiles/grass_pathEnd", [Block]),
    (
        GrassPathEndSquare,
        "base/Tiles/grass_pathEndSquare",
        [Block]
    ),
    (GrassPathSlope, "base/Tiles/grass_pathSlope", [Block]),
    (GrassPathSplit, "base/Tiles/grass_pathSplit", [Block]),
    (GrassRiver, "base/Tiles/grass_river", [Block, Water]),
    (GrassRiverBend, "base/Tiles/grass_riverBend", [Block, Water]),
    (
        GrassRiverBridge,
        "base/Tiles/grass_riverBridge",
        [Block, Water]
    ),
    (
        GrassRiverCorner,
        "base/Tiles/grass_riverCorner",
        [Block, Water]
    ),
    (
        GrassRiverCrossing,
        "base/Tiles/grass_riverCrossing",
        [Block, Water]
    ),
    (GrassRiverEnd, "base/Tiles/grass_riverEnd", [Block, Water]),
    (
        GrassRiverEndSquare,
        "base/Tiles/grass_riverEndSquare",
        [Block, Water]
    ),
    (
        GrassRiverSlope,
        "base/Tiles/grass_riverSlope",
        [Block, Water]
    ),
    (
        GrassRiverSplit,
        "base/Tiles/grass_riverSplit",
        [Block, Water]
    ),
    (GrassWater, "base/Tiles/grass_water", [Block, Water]),
    (
        GrassWaterConcave,
        "base/Tiles/grass_waterConcave",
        [Block, Water]
    ),
    (
        GrassWaterConvex,
        "base/Tiles/grass_waterConvex",
        [Block, Water]
    ),
    (
        GrassWaterRiver,
        "base/Tiles/grass_waterRiver",
        [Block, Water]
    ),
    (DesertGrass, "desert/Tiles/grass_center", [Block, Desert]),
    (
        DesertGrassSlope,
        "desert/Tiles/grass_slope",
        [Block, Desert]
    ),
    (
        DesertGrassSlopeConcave,
        "desert/Tiles/grass_slopeConcave",
        [Block, Desert]
    ),
    (
        DesertGrassSlopeConvex,
        "desert/Tiles/grass_slopeConvex",
        [Block, Desert]
    ),
    (
        DesertGrassCorner,
        "desert/Tiles/grass_corner",
        [Block, Desert]
    ),
    (DesertGrassPath, "desert/Tiles/grass_path", [Block, Desert]),
    (
        DesertGrassPathBend,
        "desert/Tiles/grass_pathBend",
        [Block, Desert]
    ),
    (
        DesertGrassPathCorner,
        "desert/Tiles/grass_pathCorner",
        [Block, Desert]
    ),
    (
        DesertGrassPathCrossing,
        "desert/Tiles/grass_pathCrossing",
        [Block, Desert]
    ),
    (
        DesertGrassPathEnd,
        "desert/Tiles/grass_pathEnd",
        [Block, Desert]
    ),
    (
        DesertGrassPathEndSquare,
        "desert/Tiles/grass_pathEndSquare",
        [Block, Desert]
    ),
    (
        DesertGrassPathSlope,
        "desert/Tiles/grass_pathSlope",
        [Block, Desert]
    ),
    (
        DesertGrassPathSplit,
        "desert/Tiles/grass_pathSplit",
        [Block, Desert]
    ),
    (
        DesertGrassRiver,
        "desert/Tiles/grass_river",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverBend,
        "desert/Tiles/grass_riverBend",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverBridge,
        "desert/Tiles/grass_riverBridge",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverCorner,
        "desert/Tiles/grass_riverCorner",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverCrossing,
        "desert/Tiles/grass_riverCrossing",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverEnd,
        "desert/Tiles/grass_riverEnd",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverEndSquare,
        "desert/Tiles/grass_riverEndSquare",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverSlope,
        "desert/Tiles/grass_riverSlope",
        [Block, Desert, Water]
    ),
    (
        DesertGrassRiverSplit,
        "desert/Tiles/grass_riverSplit",
        [Block, Desert, Water]
    ),
    (
        DesertGrassWater,
        "desert/Tiles/grass_water",
        [Block, Desert, Water]
    ),
    (
        DesertGrassWaterConcave,
        "desert/Tiles/grass_waterConcave",
        [Block, Desert, Water]
    ),
    (
        DesertGrassWaterConvex,
        "desert/Tiles/grass_waterConvex",
        [Block, Desert, Water]
    ),
    (
        DesertGrassWaterRiver,
        "desert/Tiles/grass_waterRiver",
        [Block, Desert, Water]
    ),
    (RocksDirt, "base/Tiles/rocks_dirt", [Plant]),
    (RocksGrass, "base/Tiles/rocks_grass", [Plant]),
    (RocksDesert, "desert/Tiles/rocks", [Plant, Desert]),
    (DesertOverhang, "desert/Tiles/overhang", [Building, Desert]),
    (
        DesertOverhangSmall,
        "desert/Tiles/overhang_small",
        [Building, Desert]
    ),
    (
        RoofChurchBeige,
        "base/Tiles/roof_churchBeige",
        [Building, Roof]
    ),
    (
        RoofChuchBrown,
        "base/Tiles/roof_churchBrown",
        [Building, Roof]
    ),
    (
        RoofChurchGreen,
        "base/Tiles/roof_churchGreen",
        [Building, Roof]
    ),
    (
        RoofChurchPurple,
        "base/Tiles/roof_churchPurple",
        [Building, Roof]
    ),
    (
        RoofGableBeige,
        "base/Tiles/roof_gableBeige",
        [Building, Roof]
    ),
    (
        RoofGableCornerBeige,
        "exp/Tiles/roof_gableCornerBeige",
        [Building, Roof]
    ),
    (
        RoofGableBrown,
        "base/Tiles/roof_gableBrown",
        [Building, Roof]
    ),
    (
        RoofGableCornerBrown,
        "exp/Tiles/roof_gableCornerBrown",
        [Building, Roof]
    ),
    (
        RoofGableGreen,
        "base/Tiles/roof_gableGreen",
        [Building, Roof]
    ),
    (
        RoofGableCornerGreen,
        "exp/Tiles/roof_gableCornerGreen",
        [Building, Roof]
    ),
    (
        RoofGablePurple,
        "base/Tiles/roof_gablePurple",
        [Building, Roof]
    ),
    (
        RoofGableCornerPurple,
        "exp/Tiles/roof_gableCornerPurple",
        [Building, Roof]
    ),
    (
        RoofPointBeige,
        "base/Tiles/roof_pointBeige",
        [Building, Roof]
    ),
    (
        RoofPointBrown,
        "base/Tiles/roof_pointBrown",
        [Building, Roof]
    ),
    (
        RoofPointGreen,
        "base/Tiles/roof_pointGreen",
        [Building, Roof]
    ),
    (
        RoofPointPurple,
        "base/Tiles/roof_pointPurple",
        [Building, Roof]
    ),
    (
        RoofRoundBeige,
        "base/Tiles/roof_roundBeige",
        [Building, Roof]
    ),
    (
        RoofRoundCornerBeige,
        "exp/Tiles/roof_roundCornerBeige",
        [Building, Roof]
    ),
    (
        RoofRoundBrown,
        "base/Tiles/roof_roundBrown",
        [Building, Roof]
    ),
    (
        RoofRoundCornerBrown,
        "exp/Tiles/roof_roundCornerBrown",
        [Building, Roof]
    ),
    (
        RoofRoundGreen,
        "base/Tiles/roof_roundGreen",
        [Building, Roof]
    ),
    (
        RoofRoundCornerGreen,
        "exp/Tiles/roof_roundCornerGreen",
        [Building, Roof]
    ),
    (
        RoofRoundPurple,
        "base/Tiles/roof_roundPurple",
        [Building, Roof]
    ),
    (
        RoofRoundCornerPurple,
        "exp/Tiles/roof_roundCornerPurple",
        [Building, Roof]
    ),
    (
        RoofRoundedBeige,
        "base/Tiles/roof_roundedBeige",
        [Building, Roof]
    ),
    (
        RoofRoundedBrown,
        "base/Tiles/roof_roundedBrown",
        [Building, Roof]
    ),
    (
        RoofRoundedGreen,
        "base/Tiles/roof_roundedGreen",
        [Building, Roof]
    ),
    (
        RoofRoundedPurple,
        "base/Tiles/roof_roundedPurple",
        [Building, Roof]
    ),
    (
        RoofSlantBeige,
        "base/Tiles/roof_slantBeige",
        [Building, Roof]
    ),
    (
        RoofSlantCornerBeige,
        "exp/Tiles/roof_slantCornerBeige",
        [Building, Roof]
    ),
    (
        RoofSlantCornerInnerBeige,
        "exp/Tiles/roof_slantCornerInnerBeige",
        [Building, Roof]
    ),
    (
        RoofSlantBrown,
        "base/Tiles/roof_slantBrown",
        [Building, Roof]
    ),
    (
        RoofSlantCornerBrown,
        "exp/Tiles/roof_slantCornerBrown",
        [Building, Roof]
    ),
    (
        RoofSlantCornerInnerBrown,
        "exp/Tiles/roof_slantCornerInnerBrown",
        [Building, Roof]
    ),
    (
        RoofSlantGreen,
        "base/Tiles/roof_slantGreen",
        [Building, Roof]
    ),
    (
        RoofSlantCornerGreen,
        "exp/Tiles/roof_slantCornerGreen",
        [Building, Roof]
    ),
    (
        RoofSlantCornerInnerGreen,
        "exp/Tiles/roof_slantCornerInnerGreen",
        [Building, Roof]
    ),
    (
        RoofSlantPurple,
        "base/Tiles/roof_slantPurple",
        [Building, Roof]
    ),
    (
        RoofSlantCornerPurple,
        "exp/Tiles/roof_slantCornerPurple",
        [Building, Roof]
    ),
    (
        RoofSlantCornerInnerPurple,
        "exp/Tiles/roof_slantCornerInnerPurple",
        [Building, Roof]
    ),
    (DesertDome, "desert/Tiles/dome", [Building, Desert, Roof]),
    (
        DesertDomeSmall,
        "desert/Tiles/dome_small",
        [Building, Desert, Roof]
    ),
    (DesertStairs, "desert/Tiles/stairs_full", [Building, Desert]),
    (
        DesertStairsLeft,
        "desert/Tiles/stairs_left",
        [Building, Desert]
    ),
    (
        DesertStairsRight,
        "desert/Tiles/stairs_right",
        [Building, Desert]
    ),
    (StructureArch, "base/Tiles/structure_arch", [Wood]),
    (StructureHigh, "base/Tiles/structure_high", [Wood]),
    (StructureLow, "base/Tiles/structure_low", [Wood]),
    (
        StructureTent,
        "desert/Tiles/structure_tent",
        [Building, Wood, Desert]
    ),
    (
        StructureTentSlant,
        "desert/Tiles/structure_tentSlant",
        [Building, Wood, Desert]
    ),
    (DesertTiles, "desert/Tiles/tiles", [Block, Desert]),
    (
        DesertTilesCrumbled,
        "desert/Tiles/tiles_crumbled",
        [Block, Desert]
    ),
    (
        DesertTilesDecorated,
        "desert/Tiles/tiles_decorated",
        [Block, Desert]
    ),
    (
        DesertTilesSteps,
        "desert/Tiles/tiles_steps",
        [Block, Desert]
    ),
    (Trees, "base/Tiles/tree_multiple", [Plant]),
    (Tree, "base/Tiles/tree_single", [Plant]),
    (Pine, "exp/Tiles/tree_pine", [Plant]),
    (PineLarge, "exp/Tiles/tree_pineLarge", [Plant]),
    (PalmTree, "desert/Tiles/tree", [Plant, Desert]),
    (PalmTrees, "desert/Tiles/trees", [Plant, Desert]),
    (DesertWalls, "desert/Tiles/walls_square", [Desert, Building]),
    (
        DesertWallsBroken,
        "desert/Tiles/walls_broken",
        [Desert, Building]
    ),
    (
        DesertWallsCorner,
        "desert/Tiles/walls_corner",
        [Desert, Building]
    ),
    (DesertWallsEnd, "desert/Tiles/walls_end", [Desert, Building]),
    (
        DesertWallsLeft,
        "desert/Tiles/walls_left",
        [Desert, Building]
    ),
    (
        DesertWallsRight,
        "desert/Tiles/walls_right",
        [Desert, Building]
    ),
    (WaterCenter, "base/Tiles/water_center", [Block, Water]),
    (WaterFall, "base/Tiles/water_fall", [Block, Water]),
    (Well, "exp/Tiles/well", [Water])
);
impl Default for Block {
    fn default() -> Self {


@@ 418,7 859,7 @@ impl Category {
            Self::Water => "Water & River",
            Self::Wood => "Wood",
            Self::All => "All",
            Self::Desert => "Desert"
            Self::Desert => "Desert",
        }
    }
}

M src/game/camera.rs => src/game/camera.rs +2 -2
@@ 6,14 6,14 @@ pub struct Camera {
    /// the centered pixel
    pub center: (f32, f32),
    /// scales the texture
    pub scale: f32
    pub scale: f32,
}
impl Camera {
    /// initialize new camera
    pub fn new() -> Self {
        Self {
            center: (0.0, 0.0),
            scale: 1.0
            scale: 1.0,
        }
    }
}

M src/game/inventory.rs => src/game/inventory.rs +4 -7
@@ 1,10 1,7 @@
use crate::game::blocks::{Block, Category};
use crate::game::types::Direction;
use crate::game::blocks::{
    Block,
    Category
};
use std::collections::HashMap;
use nanoserde::{DeJson, SerJson};
use std::collections::HashMap;

/// The players inventory
#[derive(SerJson, DeJson, Clone)]


@@ 19,7 16,7 @@ pub struct Inventory {
    /// in which direction the block should be facing
    pub direction: Direction,
    /// category page currently being shown
    pub category: Category
    pub category: Category,
}

impl Inventory {


@@ 29,7 26,7 @@ impl Inventory {
            contents: HashMap::new(),
            selected: None,
            direction: Direction::North,
            category: Category::All
            category: Category::All,
        }
    }
    /// adds an item to the inventory

M src/game/mod.rs => src/game/mod.rs +3 -3
@@ 1,11 1,11 @@
pub mod camera;
pub mod blocks;
pub mod camera;
pub mod inventory;
pub mod types;
pub mod world;

use crate::textures::AssetStore;
use crate::screens::Screen;
use crate::textures::AssetStore;

/// A basic Game component
/// which can be drawn


@@ 31,5 31,5 @@ pub enum GameEvent {
    /// Exit game
    Quit,
    /// Change View
    ChangeScreen(Screen)
    ChangeScreen(Screen),
}

M src/game/types.rs => src/game/types.rs +10 -15
@@ 8,35 8,30 @@ use std::cmp::Ordering;
///   /
///  /
/// x
#[derive(Debug,Eq,Hash,PartialEq, DeJson, SerJson, Clone)]
#[derive(Debug, Eq, Hash, PartialEq, DeJson, SerJson, Clone)]
pub struct Pos3 {
    pub x: i64,
    pub y: i64,
    pub z: i64
    pub z: i64,
}
impl Pos3 {
    /// initialize a new 3 dimensional position
    pub fn new(x: i64, y: i64, z: i64) -> Self {
        Self {
            x,
            y,
            z
        }
        Self { x, y, z }
    }
    pub fn cmp(&self, pos: &Self) -> Ordering {
        if self == pos {
           return Ordering::Equal;
            return Ordering::Equal;
        }

        if self.x + self.y == pos.x+pos.y {
        if self.x + self.y == pos.x + pos.y {
            return self.z.cmp(&pos.z);
        }

        (self.x+self.y).cmp(&(pos.x+pos.y))
        (self.x + self.y).cmp(&(pos.x + pos.y))
    }
}


/// Directions
#[derive(Clone, DeJson, SerJson)]
pub enum Direction {


@@ 51,7 46,7 @@ pub enum Direction {
    North,
    /// Down
    /// (bottom edge of screen)
    South
    South,
}
impl Direction {
    /// tries to extract the direction from an angle


@@ 60,9 55,9 @@ impl Direction {
            return Some(Self::North);
        } else if deg % 270 == 0 {
            return Some(Self::West);
        } else if deg%180 == 0 {
        } else if deg % 180 == 0 {
            return Some(Self::South);
        } else if deg%90 == 0 {
        } else if deg % 90 == 0 {
            return Some(Self::East);
        }



@@ 74,7 69,7 @@ impl Direction {
            Self::North => 360,
            Self::West => 270,
            Self::South => 180,
            Self::East => 90
            Self::East => 90,
        }
    }
    /// rotate counter clock wise

M src/game/world.rs => src/game/world.rs +16 -32
@@ 1,26 1,13 @@
use super::inventory::Inventory;
use super::camera::Camera;
use super::types::{
    Direction,
    Pos3
};
use super::blocks::Block;
use std::collections::HashMap;
use nanoserde::{SerJson, DeJson};
use super::camera::Camera;
use super::inventory::Inventory;
use super::types::{Direction, Pos3};
use directories_next::ProjectDirs;
use nanoserde::{DeJson, SerJson};
use std::collections::HashMap;
use std::fs::{read_dir, DirBuilder, File, OpenOptions};
use std::io::{BufReader, Read, Write};
use std::path::Path;
use std::fs::{
    File,
    OpenOptions,
    DirBuilder,
    read_dir
};
use std::io::{
    BufReader,
    Read,
    Write
};


/// returns the OS specific data dir as a string
/// if there is no such dir, None will be returned


@@ 62,7 49,7 @@ impl World {
            grid: map,
            cam: Camera::new(),
            inventory: inv,
            sandbox
            sandbox,
        }
    }



@@ 101,7 88,7 @@ impl World {

        // place block even though it is not in inventory
        if self.sandbox {
                self.grid.insert(pos.clone(), (block, dir));
            self.grid.insert(pos.clone(), (block, dir));
        }

        false


@@ 113,12 100,10 @@ impl World {
            if let Ok(rd) = &mut read_dir(dir) {
                let mut builder = Vec::new();

                for entry in rd.by_ref() {
                    if let Ok(entry) = entry {
                        if let Ok(ftype) = entry.file_type() && ftype.is_file() {
                            if let Some(name) = entry.file_name().to_str() {
                                builder.push(name.to_string());
                            }
                for entry in rd.by_ref().flatten() {
                    if let Ok(ftype) = entry.file_type() && ftype.is_file() {
                        if let Some(name) = entry.file_name().to_str() {
                            builder.push(name.to_string());
                        }
                    }
                }


@@ 154,7 139,6 @@ impl World {
        None
    }


    /// tries saving the map to disk
    pub fn to_disk(&self, name: &str) {
        if let Some(dir) = get_data_dir() {


@@ 162,9 146,10 @@ impl World {

            DirBuilder::new()
                .recursive(true)
                .create(Path::new(&dir)).unwrap();
                .create(Path::new(&dir))
                .unwrap();

            let mut file =  OpenOptions::new()
            let mut file = OpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true)


@@ 179,5 164,4 @@ impl World {
            }
        }
    }

}

M src/main.rs => src/main.rs +14 -18
@@ 9,24 9,17 @@ use macroquad::prelude::*;
mod screens;
#[macro_use]
mod textures;
mod ui;
mod game;
mod ui;

#[cfg(feature = "multiplayer")]
mod p2p;

use quad_snd::{
    Sound,
    AudioContext,
    PlaySoundParams
};
use quad_snd::{AudioContext, PlaySoundParams, Sound};

use game::{
    GameComponent,
    GameEvent
};
use textures::AssetStore;
use game::{GameComponent, GameEvent};
use screens::Screen;
use textures::AssetStore;

#[macroquad::main("Little Town")]
async fn main() {


@@ 34,13 27,16 @@ async fn main() {

    let mut screen = Screen::default();

    let mut ctx = AudioContext::new();
    let sound = Sound::load(&mut ctx, include_bytes!("../assets/music.wav"));
    let ctx = AudioContext::new();
    let sound = Sound::load(&ctx, include_bytes!("../assets/music.wav"));
    let mut vol = 0.0;
    sound.play(&mut ctx, PlaySoundParams{
        looped: true,
        volume: vol
    });
    sound.play(
        &ctx,
        PlaySoundParams {
            looped: true,
            volume: vol,
        },
    );

    loop {
        clear_background(Color::from_rgba(215, 189, 165, 255));


@@ 56,7 52,7 @@ async fn main() {
        match screen.ev_loop() {
            GameEvent::Quit => break,
            GameEvent::ChangeScreen(s) => screen = s,
            _ => ()
            _ => (),
        }

        next_frame().await

M src/p2p.rs => src/p2p.rs +151 -167
@@ 1,45 1,28 @@
use crate::game::blocks::Block;
use crate::game::types::{Direction, Pos3};
use crate::game::world::World;
use futures::{
    channel::mpsc::{self, UnboundedSender},
    prelude::stream::StreamExt,
    channel:: mpsc::{
        self,
        UnboundedSender,
    },
    select,
};
use libp2p::{
    gossipsub,
    identity,
    mdns,
    tcp,
    mplex,
    noise,
    core::upgrade,
    gossipsub, identity, mdns,
    mplex::MplexConfig,
    swarm::keep_alive,
    swarm::SwarmEvent,
    swarm::NetworkBehaviour,
    PeerId, Swarm,
    Transport
};
use std::{
    sync::{
        Arc,
        RwLock,
    },
    thread,
    time::{
        UNIX_EPOCH,
        SystemTime
    }
    swarm::{SwarmBuilder, SwarmEvent},
    tcp, PeerId, Transport,
};
use nanoserde::{DeJson, SerJson};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::Duration;
use nanoserde::{SerJson, DeJson};
use crate::game::blocks::Block;
use crate::game::world::World;
use crate::game::types::{
    Pos3,
    Direction
use std::{
    sync::{Arc, RwLock},
    thread,
    time::{SystemTime, UNIX_EPOCH},
};

/// A custom network behaviour that combines floodsub, ping and `mDNS`.


@@ 78,7 61,7 @@ enum ChannelEvent {
    /// process game action
    Action(GameAction),
    /// leave the p2p session
    Quit
    Quit,
}

/// struct to manage all relevant options for connection with poemhub p2p network


@@ 97,7 80,7 @@ pub struct Peer {
    /// the host should not change their local state
    /// when receiving Transmit* actions
    /// true if this client is the host
    is_host: bool
    is_host: bool,
}
impl Peer {
    /// create a new peer


@@ 107,7 90,7 @@ impl Peer {
            bridge: None,
            port: None,
            incoming: Arc::new(RwLock::new(Vec::new())),
            is_host: false
            is_host: false,
        };
        this.connect();
        this


@@ 119,7 102,7 @@ impl Peer {
            bridge: None,
            port: None,
            incoming: Arc::new(RwLock::new(Vec::new())),
            is_host: true
            is_host: true,
        };
        this.connect();
        this


@@ 177,168 160,169 @@ impl Peer {
    /// NOTE: Before running connect please make sure that the old instance has been stopped
    pub fn connect(&mut self) {
        let (tx, mut rx) = mpsc::unbounded::<ChannelEvent>();
        self.bridge=Some(tx);
        self.bridge = Some(tx);

        let port = self.port;
        let inbox = self.incoming.clone();
        let is_host = self.is_host;

        thread::spawn(move ||async_std::task::block_on(async {
            let local_key = identity::Keypair::generate_ed25519();
            let local_peer_id = PeerId::from(local_key.public());
        thread::spawn(move || {
            async_std::task::block_on(async {
                let local_key = identity::Keypair::generate_ed25519();
                let local_peer_id = PeerId::from(local_key.public());

            let transport = tcp::async_io::Transport::new(tcp::Config::default().nodelay(true))
                .upgrade(upgrade::Version::V1)
                .authenticate(
                    noise::NoiseAuthenticated::xx(&local_key)
                        .expect("Signing libp2p-noise static DH keypair failed."),
                )
                .multiplex(mplex::MplexConfig::new())
                .boxed();
                let transport = tcp::async_io::Transport::new(tcp::Config::default().nodelay(true))
                    .upgrade(upgrade::Version::V1)
                    .authenticate(libp2p::noise::Config::new(&local_key).unwrap())
                    .multiplex(MplexConfig::new())
                    .boxed();

            // To content-address message, we can take the hash of message and use it as an ID.
            let message_id_fn = |message: &gossipsub::Message| {
                let mut s = DefaultHasher::new();
                message.data.hash(&mut s);
                gossipsub::MessageId::from(s.finish().to_string())
            };
                // To content-address message, we can take the hash of message and use it as an ID.
                let message_id_fn = |message: &gossipsub::Message| {
                    let mut s = DefaultHasher::new();
                    message.data.hash(&mut s);
                    gossipsub::MessageId::from(s.finish().to_string())
                };

            // Set a custom gossipsub configuration
            let gossipsub_config = gossipsub::ConfigBuilder::default()
                .heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space
                .validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing)
                .message_id_fn(message_id_fn) // content-address messages. No two messages of the same content will be propagated.
                .build()
                .expect("Valid config");
                // Set a custom gossipsub configuration
                let gossipsub_config = gossipsub::ConfigBuilder::default()
                    .heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space
                    .validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing)
                    .message_id_fn(message_id_fn) // content-address messages. No two messages of the same content will be propagated.
                    .build()
                    .expect("Valid config");

            let mut gossipsub = gossipsub::Behaviour::new(
                gossipsub::MessageAuthenticity::Signed(local_key),
                gossipsub_config,
            )
                let mut gossipsub = gossipsub::Behaviour::new(
                    gossipsub::MessageAuthenticity::Signed(local_key),
                    gossipsub_config,
                )
                .expect("Correct configuration");

            // Create a topic
            let topic = gossipsub::IdentTopic::new(TOPIC);
            gossipsub.subscribe(&topic);
                // Create a topic
                let topic = gossipsub::IdentTopic::new(TOPIC);
                let _ = gossipsub.subscribe(&topic);

            // Create a Swarm to manage peers and events
            let mut swarm = {
                let mdns = mdns::Behaviour::new(mdns::Config::default(), local_peer_id).unwrap();
                // Create a Swarm to manage peers and events
                let mut swarm = {
                    let mdns =
                        mdns::Behaviour::new(mdns::Config::default(), local_peer_id).unwrap();

                let behaviour = PeerBehaviour {
                    gossipsub,
                    mdns,
                    keep_alive: Default::default()
                    let behaviour = PeerBehaviour {
                        gossipsub,
                        mdns,
                        keep_alive: Default::default(),
                    };
                    SwarmBuilder::with_async_std_executor(transport, behaviour, local_peer_id)
                        .build()
                };
                Swarm::with_async_std_executor(transport, behaviour, local_peer_id)
            };

            // Listen on all interfaces and whatever port the OS assigns
            quad_rand::srand(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
            let port = port.unwrap_or(quad_rand::gen_range(4000, 9999));
            println!("Setting up port {}", port);
            swarm.listen_on(
                format!(
                    "/ip4/0.0.0.0/tcp/{}",port)
                    .parse()
                    .expect("Failed to parse provided port"))
                 .expect("Failed to start p2p network");
                // Listen on all interfaces and whatever port the OS assigns
                quad_rand::srand(
                    SystemTime::now()
                        .duration_since(UNIX_EPOCH)
                        .unwrap()
                        .as_secs(),
                );
                let port = port.unwrap_or(quad_rand::gen_range(4000, 9999));
                println!("Setting up port {}", port);
                swarm
                    .listen_on(
                        format!("/ip4/0.0.0.0/tcp/{}", port)
                            .parse()
                            .expect("Failed to parse provided port"),
                    )
                    .expect("Failed to start p2p network");

            loop {
                select!{
                    bridge_event = rx.select_next_some() => match bridge_event {
                        ChannelEvent::Action(act) => {
                            let json = act.serialize_json();
                            swarm
                                .behaviour_mut()
                                .gossipsub
                                .publish(topic.clone(), json.as_bytes());
                loop {
                    select! {
                        bridge_event = rx.select_next_some() => match bridge_event {
                            ChannelEvent::Action(act) => {
                                let json = act.serialize_json();
                                let _ = swarm
                                    .behaviour_mut()
                                    .gossipsub
                                    .publish(topic.clone(), json.as_bytes());
                            },
                            ChannelEvent::Quit => break,
                        },
                        ChannelEvent::Quit => break,
                    },

                    event = swarm.select_next_some() => match event {
                        SwarmEvent::NewListenAddr { address, .. } => {
                            println!("Listening on {:?}", address);
                        }
                        SwarmEvent::Behaviour(PeerBehaviourEvent::Gossipsub(
                            gossipsub::Event::Message{
                                propagation_source: _,
                                message_id: _,
                                message
                        event = swarm.select_next_some() => match event {
                            SwarmEvent::NewListenAddr { address, .. } => {
                                println!("Listening on {:?}", address);
                            }
                        )) => {
                            // parse incoming action
                            let text = String::from_utf8_lossy(&message.data);
                            if let Ok(act) = GameAction::deserialize_json(&text) {
                                // hand on act to game instance
                                let may_process = if is_host {
                                    match act {
                                        GameAction::TransmitWorld(_) => false,
                                        _ => true
                            SwarmEvent::Behaviour(PeerBehaviourEvent::Gossipsub(
                                gossipsub::Event::Message{
                                    propagation_source: _,
                                    message_id: _,
                                    message
                                }
                            )) => {
                                // parse incoming action
                                let text = String::from_utf8_lossy(&message.data);
                                if let Ok(act) = GameAction::deserialize_json(&text) {
                                    // hand on act to game instance
                                    let may_process = if is_host {
                                        !matches!(act, GameAction::TransmitWorld(_))
                                    } else {
                                        !matches!(act, GameAction::RequestWorld)
                                    };

                                    if may_process {
                                        println!("Adding act to queue");
                                        inbox.write().unwrap().push(act);
                                    }
                                } else {
                                    match act {
                                        GameAction::RequestWorld => false,
                                        _ => true
                                }
                            },
                            SwarmEvent::Behaviour(PeerBehaviourEvent::Gossipsub(
                                gossipsub::Event::Subscribed {..}
                            )) => {
                                println!("Subscribed");
                                // ask game to broadcast world map and inventory
                                // game should then send ChannelEvent::Action as quickly as possible
                                if !is_host {
                                    // request data
                                    // in case host lost the request
                                    {
                                        let act = GameAction::RequestWorld;
                                        let json = act.serialize_json();
                                        let _ = swarm
                                            .behaviour_mut()
                                            .gossipsub
                                            .publish(topic.clone(), json.as_bytes());
                                    }
                                };

                                if may_process {
                                    println!("Adding act to queue");
                                    inbox.write().unwrap().push(act);
                                }
                            }
                        },
                        SwarmEvent::Behaviour(PeerBehaviourEvent::Gossipsub(
                            gossipsub::Event::Subscribed {..}
                        )) => {
                            println!("Subscribed");
                            // ask game to broadcast world map and inventory
                            // game should then send ChannelEvent::Action as quickly as possible
                            if !is_host {
                                // request data
                                // in case host lost the request
                                {
                                    let act = GameAction::RequestWorld;
                                    let json = act.serialize_json();
                            },
                            SwarmEvent::Behaviour(PeerBehaviourEvent::Mdns(
                                mdns::Event::Discovered(list)
                            )) => {
                                for (peer, _) in list {
                                    println!("Discovered {:?}", peer);
                                    swarm
                                        .behaviour_mut()
                                        .gossipsub
                                        .publish(topic.clone(), json.as_bytes());
                                        .add_explicit_peer(&peer);
                                }
                            }
                        },
                        SwarmEvent::Behaviour(PeerBehaviourEvent::Mdns(
                            mdns::Event::Discovered(list)
                        )) => {
                            for (peer, _) in list {
                                println!("Discovered {:?}", peer);
                                swarm
                                    .behaviour_mut()
                                    .gossipsub
                                    .add_explicit_peer(&peer);
                            }
                        }
                        SwarmEvent::Behaviour(PeerBehaviourEvent::Mdns(
                            mdns::Event::Expired(
                                list
                            ))) => {
                            for (peer, _) in list {
                                println!("Disconnected {:?}", peer);
                                if !swarm.behaviour_mut().mdns.has_node(&peer) {
                                    swarm
                                        .behaviour_mut()
                                        .gossipsub
                                        .remove_explicit_peer(&peer);
                            SwarmEvent::Behaviour(PeerBehaviourEvent::Mdns(
                                mdns::Event::Expired(
                                    list
                                ))) => {
                                for (peer, _) in list {
                                    println!("Disconnected {:?}", peer);
                                    if !swarm.behaviour_mut().mdns.has_node(&peer) {
                                        swarm
                                            .behaviour_mut()
                                            .gossipsub
                                            .remove_explicit_peer(&peer);
                                    }
                                }
                            }
                        },
                        // ping events can be ignored
                        _ => {}
                            },
                            // ping events can be ignored
                            _ => {}
                        }
                    }
                }
            }
        }));
            })
        });
    }
}

M src/screens/build.rs => src/screens/build.rs +238 -243
@@ 1,29 1,17 @@
use macroquad::prelude::*;
use crate::textures::AssetStore;
use super::Screen;
use crate::game::{
    GameComponent,
    GameEvent,
    blocks::{Block, Category, Face},
    inventory::Inventory,
    blocks::{Face, Block, Category},
    types::{Direction, Pos3},
    world::World,
    types::{
        Pos3,
        Direction
    }};
use crate::ui::{
    ItemFrame,
    ButtonEvent,
    Widget,
    CenteredText,
    TextButton
    GameComponent, GameEvent,
};
use super::Screen;
use crate::textures::AssetStore;
use crate::ui::{ButtonEvent, CenteredText, ItemFrame, TextButton, Widget};
use macroquad::prelude::*;

#[cfg(feature = "multiplayer")]
use crate::p2p::{
    Peer,
    GameAction
};
use crate::p2p::{GameAction, Peer};

/// image width
pub const TEXTURE_WIDTH: f32 = 256.0;


@@ 49,15 37,11 @@ fn get_screen_coords(pos: &Pos3, scale: f32, center: (f32, f32)) -> (f32, f32) {
    let dx = pos.y - pos.x;
    let dy = pos.y + pos.x;

    let x = screen_width() / 2.0
        + dx as f32 * w_i / 2.0
        + center.0
        - width / 2.0;
    let y = (pos.z as f32).mul_add(-h_i, screen_height() / 2.0 + dy as f32 * h_i / 2.0)
        + center.1
    let x = screen_width() / 2.0 + dx as f32 * w_i / 2.0 + center.0 - width / 2.0;
    let y = (pos.z as f32).mul_add(-h_i, screen_height() / 2.0 + dy as f32 * h_i / 2.0) + center.1
        - height / 2.0;

    (x,y)
    (x, y)
}

struct InventoryWidgets {


@@ 76,7 60,7 @@ struct InventoryWidgets {
    /// and on how many slots to draw per page
    /// see SLOTS_PER_PAGE for more
    page: usize,
    pub shown: bool
    pub shown: bool,
}
impl InventoryWidgets {
    fn new() -> Self {


@@ 87,11 71,9 @@ impl InventoryWidgets {
                ItemFrame::new(0.35, 0.35, size),
                ItemFrame::new(0.5, 0.35, size),
                ItemFrame::new(0.65, 0.35, size),

                ItemFrame::new(0.35, 0.5, size),
                ItemFrame::new(0.5, 0.5, size),
                ItemFrame::new(0.65, 0.5, size),

                ItemFrame::new(0.35, 0.65, size),
                ItemFrame::new(0.5, 0.65, size),
                ItemFrame::new(0.65, 0.65, size),


@@ 99,7 81,7 @@ impl InventoryWidgets {
            widget_back: TextButton::new("Back", 0.2, 0.2),
            widget_categories: TextButton::new("All", 0.8, 0.2),
            widget_page_indicator: CenteredText::new("0", 0.5, 0.8),
            page: 0
            page: 0,
        }
    }
    /// loads the first page for the last used category


@@ 108,21 90,20 @@ impl InventoryWidgets {
    }
    /// loads a new category
    fn load_category(&mut self, cat: Category, inv: &mut Inventory) {
        let page = if inv.category != cat {
            0
        } else { self.page };
        let page = if inv.category != cat { 0 } else { self.page };
        inv.category = cat;
        let title = inv.category.get_name();
        self.widget_categories.set_text(title);
        let list = inv.category.get_blocks();
        let page_count = list.len().div_ceil(9);
        self.widget_page_indicator.set_text(&format!("{}/{}", self.page+1, page_count));
        self.widget_categories.set_text(&format!("{title}"));
        self.widget_page_indicator
            .set_text(&format!("{}/{}", self.page + 1, page_count));
        self.widget_categories.set_text(title);

        self.load_page(page, inv);
    }
    /// loads the entries of the given page in the current category
    fn load_page(&mut self, page: usize, inv: &mut Inventory) {
    fn load_page(&mut self, page: usize, inv: &Inventory) {
        self.page = page;
        let list = inv.category.get_blocks();
        let max_page = list.len().div_ceil(9);


@@ 134,21 115,22 @@ impl InventoryWidgets {
                slot.empty();
            }
        }
        self.widget_page_indicator.set_text(&format!("{}/{}", self.page+1, max_page));
        self.widget_page_indicator
            .set_text(&format!("{}/{}", self.page + 1, max_page));
    }
    /// go one page down
    fn page_down(&mut self, inv: &mut Inventory) {
    fn page_down(&mut self, inv: &Inventory) {
        let list = inv.category.get_blocks();
        let mut page = self.page;
        if page > 0 {
            page -= 1;
        } else {
            page = list.len().div_ceil(9)-1;
            page = list.len().div_ceil(9) - 1;
        }
        self.load_page(page, inv);
    }
    /// go one page up
    fn page_up(&mut self, inv: &mut Inventory) {
    fn page_up(&mut self, inv: &Inventory) {
        let list = inv.category.get_blocks();
        let mut page = self.page;
        let max_page = list.len().div_ceil(9);


@@ 170,21 152,21 @@ pub struct BuildScreen {
    /// where the mouse is currently at
    mouse_position: Option<(f32, f32)>,
    /// the p2p backend used for multiplayer mode
    #[cfg(feature="multiplayer")]
    #[cfg(feature = "multiplayer")]
    multiplayer: Option<Peer>,
    /// indicator widget showing the currently selected item and its orientation
    item_indicator_widget: ItemFrame,
    /// show the inventory popup
    inventory_widgets: InventoryWidgets
    inventory_widgets: InventoryWidgets,
}
impl BuildScreen {
    /// attempts to load a world from disk
    pub fn from_disk(file_name: &str) -> Option<Self> {
        World::from_disk(file_name).map(|world| Self {
                world: Some(world),
                file_name: Some(file_name.to_string()),
                ..Self::empty()
            })
            world: Some(world),
            file_name: Some(file_name.to_string()),
            ..Self::empty()
        })
    }
    /// create a new empty world
    pub fn new(file_name: &str, sandbox: bool) -> Self {


@@ 201,10 183,10 @@ impl BuildScreen {
            world: None,
            mouse_position: None,
            file_name: None,
            #[cfg(feature="multiplayer")]
            #[cfg(feature = "multiplayer")]
            multiplayer: None,
            item_indicator_widget: ItemFrame::new(0.95, 0.05, 0.05),
            inventory_widgets: InventoryWidgets::new()
            inventory_widgets: InventoryWidgets::new(),
        }
    }
    /// Creates an empty build screen


@@ 212,7 194,7 @@ impl BuildScreen {
    /// when the World transmission is received,
    /// the build screen will be upgraded
    /// to a room with map
    #[cfg(feature="multiplayer")]
    #[cfg(feature = "multiplayer")]
    pub fn join() -> Self {
        Self {
            multiplayer: Some(Peer::client()),


@@ 226,8 208,8 @@ impl BuildScreen {
    }
    /// Launch an existing build screen in game host mode
    /// initializes the p2p backend
    #[cfg(feature="multiplayer")]
    pub fn as_host(self) -> Self {
    #[cfg(feature = "multiplayer")]
    pub fn with_host_mode(self) -> Self {
        Self {
            multiplayer: Some(Peer::host()),
            ..self


@@ 246,7 228,10 @@ impl GameComponent for BuildScreen {
                    slot.draw(assets).await;
                }

                self.inventory_widgets.widget_page_indicator.draw(assets).await;
                self.inventory_widgets
                    .widget_page_indicator
                    .draw(assets)
                    .await;

                return;
            }


@@ 260,25 245,25 @@ impl GameComponent for BuildScreen {
                let width = TEXTURE_WIDTH * world.cam.scale;
                let height = TEXTURE_HEIGHT * world.cam.scale;

                let (x,y) = get_screen_coords(pos, world.cam.scale, world.cam.center);

                if x >= -width && x <= screen_width()
                    && y >= -height && y <= screen_height() {
                        // render block
                        draw_texture_ex(
                            texture,
                            x,
                            y,
                            WHITE,
                            DrawTextureParams {
                                dest_size: Some(Vec2::new(width, height)),
                                source: None,
                                rotation: 0.0,
                                flip_x: false,
                                flip_y: false,
                                pivot: Some(Vec2::new(width / 2.0, height / 2.0))
                            });
                    }
                let (x, y) = get_screen_coords(pos, world.cam.scale, world.cam.center);

                if x >= -width && x <= screen_width() && y >= -height && y <= screen_height() {
                    // render block
                    draw_texture_ex(
                        texture,
                        x,
                        y,
                        WHITE,
                        DrawTextureParams {
                            dest_size: Some(Vec2::new(width, height)),
                            source: None,
                            rotation: 0.0,
                            flip_x: false,
                            flip_y: false,
                            pivot: Some(Vec2::new(width / 2.0, height / 2.0)),
                        },
                    );
                }
            }

            // Draw selected item (top right)


@@ 289,202 274,211 @@ impl GameComponent for BuildScreen {
        if let Some(world) = &mut self.world {
            if self.inventory_widgets.shown {
                // process input
                match self.inventory_widgets.widget_back.ev_loop() {
                    ButtonEvent::LeftClick => {
                        self.inventory_widgets.shown = false;
                    }
                    _ => ()
                if let ButtonEvent::LeftClick = self.inventory_widgets.widget_back.ev_loop() {
                    self.inventory_widgets.shown = false;
                }
                if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::I) {
                if is_key_pressed(KeyCode::Q)
                    || is_key_pressed(KeyCode::Escape)
                    || is_key_pressed(KeyCode::I)
                {
                    self.inventory_widgets.shown = false;
                }

                if is_key_pressed(KeyCode::Left) {
                    self.inventory_widgets.page_down(&mut world.inventory);
                    self.inventory_widgets.page_down(&world.inventory);
                }
                if is_key_pressed(KeyCode::Right) {
                    self.inventory_widgets.page_up(&mut world.inventory);
                    self.inventory_widgets.page_up(&world.inventory);
                }

                match self.inventory_widgets.widget_categories.ev_loop() {
                    ButtonEvent::LeftClick => {
                        // change category
                        let categories = Category::all();
                        let current = &world.inventory.category;
                        let index = categories.iter().position(|c| &c == &current).unwrap();
                        if index + 1 >= categories.len() {
                            self.inventory_widgets.load_category(
                                categories.get(0).unwrap().clone(),
                                &mut world.inventory
                            );
                        } else {
                            self.inventory_widgets.load_category(
                                categories.get(index+1).unwrap().clone(),
                                &mut world.inventory
                            );
                        }
                if let ButtonEvent::LeftClick = self.inventory_widgets.widget_categories.ev_loop() {
                    // change category
                    let categories = Category::all();
                    let current = &world.inventory.category;
                    let index = categories.iter().position(|c| c == current).unwrap();
                    if index + 1 >= categories.len() {
                        self.inventory_widgets.load_category(
                            categories.get(0).unwrap().clone(),
                            &mut world.inventory,
                        );
                    } else {
                        self.inventory_widgets.load_category(
                            categories.get(index + 1).unwrap().clone(),
                            &mut world.inventory,
                        );
                    }
                    _ => ()
                }

                for (i, slot) in self.inventory_widgets.widgets_slots.iter_mut().enumerate() {
                    slot.update_dir(&world.inventory.direction);

                    match slot.ev_loop() {
                        ButtonEvent::LeftClick => {
                            // user selected block
                            let items = world.inventory.category.get_blocks();
                            if let Some(block) = items.get(self.inventory_widgets.page * 9 + i) {
                                world.inventory.selected = Some(block.clone());
                                self.inventory_widgets.shown = false;
                            }
                        },
                        _ => ()
                    if let ButtonEvent::LeftClick = slot.ev_loop() {
                        // user selected block
                        let items = world.inventory.category.get_blocks();
                        if let Some(block) = items.get(self.inventory_widgets.page * 9 + i) {
                            world.inventory.selected = Some(block.clone());
                            self.inventory_widgets.shown = false;
                        }
                    }
                }

            } else {
                // process inventory events
                if is_key_down(KeyCode::I) {
                    // open inventory
                    self.inventory_widgets.shown = true;
                    self.inventory_widgets.load_current_category(&mut world.inventory);
                    self.inventory_widgets
                        .load_current_category(&mut world.inventory);
                }

                match self.item_indicator_widget.ev_loop() {
                    ButtonEvent::LeftClick => {
                        // open inventory
                        self.inventory_widgets.shown = true;
                        self.inventory_widgets.load_current_category(&mut world.inventory);
                    }
                    _ => ()
                if let ButtonEvent::LeftClick = self.item_indicator_widget.ev_loop() {
                    // open inventory
                    self.inventory_widgets.shown = true;
                    self.inventory_widgets
                        .load_current_category(&mut world.inventory);
                }

                // update the top-right item indicator
                {
                    let amount = if let Some(block) = &world.inventory.selected {
                        world.inventory.contents.get(block).copied()
                    } else { None };
                    self.item_indicator_widget.update(&world.inventory.selected, &world.inventory.direction, &amount);
                    } else {
                        None
                    };
                    self.item_indicator_widget.update(
                        &world.inventory.selected,
                        &world.inventory.direction,
                        &amount,
                    );
                }

                if is_mouse_button_pressed(MouseButton::Left)
                    || is_mouse_button_pressed(MouseButton::Right) {

                        // determine which block / side was clicked
                        let (mx, my) = mouse_position();

                        // virtual render cycle
                        let render_order: Vec<(&Pos3, &(Block, Direction))> = world.grid.iter().collect();

                        // list of positions in render que for given pixel
                        // (mx, my)
                        let mut in_path:Vec<&Pos3> = Vec::new();

                        for (pos, _) in &render_order {
                            let (x,y) = get_screen_coords(pos, world.cam.scale, world.cam.center);

                            if mx >= x
                                && mx <= TEXTURE_WIDTH.mul_add(world.cam.scale, x)
                                && my >= TEXTURE_Y_WHITESPACE.mul_add(world.cam.scale, y)
                                && my <= TEXTURE_HEIGHT.mul_add(world.cam.scale, y) {

                                    // check if mouse is above transparent area
                                    // and skip block if necessary
                                    if Face::from_xy(mx-x, my-y, world.cam.scale).is_none() {
                                        continue;
                                    }
                    || is_mouse_button_pressed(MouseButton::Right)
                {
                    // determine which block / side was clicked
                    let (mx, my) = mouse_position();

                    // virtual render cycle
                    let render_order: Vec<(&Pos3, &(Block, Direction))> =
                        world.grid.iter().collect();

                    // list of positions in render que for given pixel
                    // (mx, my)
                    let mut in_path: Vec<&Pos3> = Vec::new();

                    for (pos, _) in &render_order {
                        let (x, y) = get_screen_coords(pos, world.cam.scale, world.cam.center);

                        if mx >= x
                            && mx <= TEXTURE_WIDTH.mul_add(world.cam.scale, x)
                            && my >= TEXTURE_Y_WHITESPACE.mul_add(world.cam.scale, y)
                            && my <= TEXTURE_HEIGHT.mul_add(world.cam.scale, y)
                        {
                            // check if mouse is above transparent area
                            // and skip block if necessary
                            if Face::from_xy(mx - x, my - y, world.cam.scale).is_none() {
                                continue;
                            }

                                    // block in mouse path
                                    in_path.push(pos);
                                }
                            // block in mouse path
                            in_path.push(pos);
                        }
                        // weight axis
                        in_path.sort_by(|p1, p2| p1.cmp(p2));
                        if let Some(pos) = in_path.last() {
                            // position of clicked block
                            // because it is the last block in de render queue
                            // for a given pixel
                            let mut pos = Pos3::new(pos.x, pos.y, pos.z);

                            if is_key_down(KeyCode::LeftControl) {
                                if let Some(block) = world.grid.get_mut(&pos) {
                                    if is_mouse_button_pressed(MouseButton::Left) {
                                        // rotate block CCW
                                        block.1 = block.1.rotate_ccw();
                                    } else if is_mouse_button_pressed(MouseButton::Right) {
                                        // rotate block CW
                                        block.1 = block.1.rotate_cw();
                                    }
                    }
                    // weight axis
                    in_path.sort_by(|p1, p2| p1.cmp(p2));
                    if let Some(pos) = in_path.last() {
                        // position of clicked block
                        // because it is the last block in de render queue
                        // for a given pixel
                        let mut pos = Pos3::new(pos.x, pos.y, pos.z);

                        if is_key_down(KeyCode::LeftControl) {
                            if let Some(block) = world.grid.get_mut(&pos) {
                                if is_mouse_button_pressed(MouseButton::Left) {
                                    // rotate block CCW
                                    block.1 = block.1.rotate_ccw();
                                } else if is_mouse_button_pressed(MouseButton::Right) {
                                    // rotate block CW
                                    block.1 = block.1.rotate_cw();
                                }
                            } else {
                                if is_mouse_button_pressed(MouseButton::Right) {
                                    // if grid has only one block
                                    // the block can not be removed
                                    // because the palyer would not be able to place
                                    // new blocks if no block exists
                                    if world.grid.len() > 1 {

                                        // destroy block
                                        if let Some((block, _)) = world.destroy_block(&pos) {
                                            // transmit block destruction
                                            // if multiplayer mode enabled
                                            #[cfg(feature = "multiplayer")]
                                            #[cfg(feature = "multiplayer")]
                                            if let Some(mp) = &mut self.multiplayer {
                                                mp.perform_action(GameAction::RemoveBlock(pos.clone(), block));
                                            }
                            }
                        } else {
                            if is_mouse_button_pressed(MouseButton::Right) {
                                // if grid has only one block
                                // the block can not be removed
                                // because the palyer would not be able to place
                                // new blocks if no block exists
                                if world.grid.len() > 1 {
                                    // destroy block
                                    if let Some((block, _)) = world.destroy_block(&pos) {
                                        // transmit block destruction
                                        // if multiplayer mode enabled
                                        #[cfg(feature = "multiplayer")]
                                        #[cfg(feature = "multiplayer")]
                                        if let Some(mp) = &mut self.multiplayer {
                                            mp.perform_action(GameAction::RemoveBlock(
                                                pos.clone(),
                                                block,
                                            ));
                                        }
                                    }
                                }
                            }

                                if is_mouse_button_pressed(MouseButton::Left) {
                                    // place block
                                    let (x,y) = get_screen_coords(&pos, world.cam.scale, world.cam.center);
                                    let face = Face::from_xy(mx-x, my-y, world.cam.scale);

                                    // invert coordinate when left shift is pressed
                                    // allows placing blocks below & behind other block
                                    let factor = if is_key_down(KeyCode::LeftShift) {
                                        -1
                                    } else {
                                        1
                                    };

                                    match face.unwrap() {
                                        Face::Top => {
                                            pos.z+=factor;
                                        },
                                        Face::Left => {
                                            pos.x+=factor;
                                        },
                                        Face::Right => {
                                            pos.y+=factor;
                                        }
                            if is_mouse_button_pressed(MouseButton::Left) {
                                // place block
                                let (x, y) =
                                    get_screen_coords(&pos, world.cam.scale, world.cam.center);
                                let face = Face::from_xy(mx - x, my - y, world.cam.scale);

                                // invert coordinate when left shift is pressed
                                // allows placing blocks below & behind other block
                                let factor = if is_key_down(KeyCode::LeftShift) {
                                    -1
                                } else {
                                    1
                                };

                                match face.unwrap() {
                                    Face::Top => {
                                        pos.z += factor;
                                    }
                                    Face::Left => {
                                        pos.x += factor;
                                    }
                                    Face::Right => {
                                        pos.y += factor;
                                    }
                                }

                                    if let Some(block) = &world.inventory.selected.clone() {
                                        if world.place_block(&pos, block.clone(), world.inventory.direction.clone()) {

                                            // transmit block placement
                                            // if multiplayer mode enabled
                                            #[cfg(feature = "multiplayer")]
                                            if let Some(mp) = &mut self.multiplayer {
                                                mp.perform_action(GameAction::PlaceBlock(pos, block.clone(), world.inventory.direction.clone()));
                                            }
                                if let Some(block) = &world.inventory.selected.clone() {
                                    if world.place_block(
                                        &pos,
                                        block.clone(),
                                        world.inventory.direction.clone(),
                                    ) {
                                        // transmit block placement
                                        // if multiplayer mode enabled
                                        #[cfg(feature = "multiplayer")]
                                        if let Some(mp) = &mut self.multiplayer {
                                            mp.perform_action(GameAction::PlaceBlock(
                                                pos,
                                                block.clone(),
                                                world.inventory.direction.clone(),
                                            ));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                // zoom with Ctrl-MouseWheel
                if is_key_down(KeyCode::LeftControl) {
                    let mut scale = world.cam.scale;
                    let (_, wy) = mouse_wheel();

                    scale += wy * 1.0/10.0;
                    scale += wy * 1.0 / 10.0;

                    if scale > 0.05 && scale < 6.0 {
                        world.cam.scale = scale;


@@ 492,22 486,22 @@ impl GameComponent for BuildScreen {
                } else {
                    let (mx, my) = mouse_wheel();

                    world.cam.center.0 += mx * 5.0 * (1.0/world.cam.scale);
                    world.cam.center.1 += my * 5.0 * (1.0/world.cam.scale);
                    world.cam.center.0 += mx * 5.0 * (1.0 / world.cam.scale);
                    world.cam.center.1 += my * 5.0 * (1.0 / world.cam.scale);
                }

                // keyboard control
                if is_key_down(KeyCode::Down) {
                    world.cam.center.1 -= 1.0 * (1.0/world.cam.scale);
                    world.cam.center.1 -= 1.0 * (1.0 / world.cam.scale);
                }
                if is_key_down(KeyCode::Up) {
                    world.cam.center.1 += 1.0 * (1.0/world.cam.scale);
                    world.cam.center.1 += 1.0 * (1.0 / world.cam.scale);
                }
                if is_key_down(KeyCode::Left) {
                    world.cam.center.0 += 1.0 * (1.0/world.cam.scale);
                    world.cam.center.0 += 1.0 * (1.0 / world.cam.scale);
                }
                if is_key_down(KeyCode::Right) {
                    world.cam.center.0 -= 1.0 * (1.0/world.cam.scale);
                    world.cam.center.0 -= 1.0 * (1.0 / world.cam.scale);
                }
                if is_key_down(KeyCode::C) {
                    world.cam.center = (0.0, 0.0);


@@ 515,7 509,6 @@ impl GameComponent for BuildScreen {
                if is_mouse_button_down(MouseButton::Middle) {
                    let (nx, ny) = mouse_position();
                    if let Some((ox, oy)) = self.mouse_position {

                        let dx = nx - ox;
                        let dy = ny - oy;



@@ 531,24 524,24 @@ impl GameComponent for BuildScreen {
                // save the world
                if is_key_pressed(KeyCode::S)
                    || is_key_pressed(KeyCode::Q)
                    || is_key_pressed(KeyCode::Escape) {
                        // save game

                        if let Some(file_name) = &self.file_name {
                            world.to_disk(file_name);
                        }

                        if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) {
                    || is_key_pressed(KeyCode::Escape)
                {
                    // save game

                            #[cfg(feature = "multiplayer")]
                            if let Some(mp) = &mut self.multiplayer {
                                mp.close();
                            }
                    if let Some(file_name) = &self.file_name {
                        world.to_disk(file_name);
                    }

                            // close game
                            return GameEvent::ChangeScreen(Screen::default());
                    if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) {
                        #[cfg(feature = "multiplayer")]
                        if let Some(mp) = &mut self.multiplayer {
                            mp.close();
                        }

                        // close game
                        return GameEvent::ChangeScreen(Screen::default());
                    }
                }
            }

            // change block direction


@@ 569,12 562,15 @@ impl GameComponent for BuildScreen {
            }
        } else {
            // game has no world
            if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) || is_key_pressed(KeyCode::I) {
            if is_key_pressed(KeyCode::Q)
                || is_key_pressed(KeyCode::Escape)
                || is_key_pressed(KeyCode::I)
            {
                return GameEvent::Quit;
            }
        }

        #[cfg(feature="multiplayer")]
        #[cfg(feature = "multiplayer")]
        {
            // maybe user is waiting in lobby
            // still check for p2p connections


@@ 585,24 581,23 @@ impl GameComponent for BuildScreen {
                    match task {
                        GameAction::RequestWorld => {
                            if let Some(world) = &mut self.world {
                                mp.perform_action(
                                    GameAction::TransmitWorld(world.clone()));
                                mp.perform_action(GameAction::TransmitWorld(world.clone()));
                            }
                        },
                        }
                        GameAction::PlaceBlock(pos, block, dir) => {
                            if let Some(world) = &mut self.world {
                                // remove block from inventory
                                world.place_block(&pos, block, dir);
                            }
                        },
                        }
                        GameAction::RemoveBlock(pos, _block) => {
                            if let Some(world) = &mut self.world {
                                world.destroy_block(&pos);
                            }
                        },
                        }
                        GameAction::TransmitWorld(world) => {
                            self.upgrade(world);
                        },
                        }
                    }
                }
            }

M src/screens/map_creator.rs => src/screens/map_creator.rs +34 -45
@@ 1,20 1,11 @@
use macroquad::prelude::*;
use crate::game::{
    GameComponent,
    GameEvent,
    world::World
};
use crate::textures::AssetStore;
use super::map_select::SelectScreen;
use super::build::BuildScreen;
use super::map_select::SelectScreen;
use super::Screen;
use crate::game::{world::World, GameComponent, GameEvent};
use crate::textures::AssetStore;
use macroquad::prelude::*;

use crate::ui::{
    TextButton,
    Widget,
    ButtonEvent,
    SelectableText
};
use crate::ui::{ButtonEvent, SelectableText, TextButton, Widget};

/// The welcome screen
pub struct MapCreatorScreen {


@@ 27,7 18,7 @@ pub struct MapCreatorScreen {
    /// and return to world list
    widget_create: TextButton,
    /// String contianing the world name
    name: String
    name: String,
}
impl MapCreatorScreen {
    /// create a new World creation Screen instance


@@ 37,31 28,37 @@ impl MapCreatorScreen {
                .with_font_size(50)
                .with_font_color(LIGHTGRAY),
            widget_container: SelectableText::new("type here...", 0.5, 0.5),
            widget_create: TextButton::new("Create", 0.7, 0.7)
                .with_font_size(40),
            name: String::new()
            widget_create: TextButton::new("Create", 0.7, 0.7).with_font_size(40),
            name: String::new(),
        }
    }
}
impl GameComponent for MapCreatorScreen {
    async fn draw(&self, assets: &AssetStore) {

        {
            // draw background image
            let dim: f32 = if screen_width() < screen_height() {
                screen_width() * 2.0/3.0
            } else { screen_height() * 2.0/3.0 };
                screen_width() * 2.0 / 3.0
            } else {
                screen_height() * 2.0 / 3.0
            };
            draw_texture_ex(
                assets.icon,
                screen_width()/2.0 - dim/2.0,
                screen_height()/2.0 - dim/2.0,
                screen_width() / 2.0 - dim / 2.0,
                screen_height() / 2.0 - dim / 2.0,
                WHITE,
                DrawTextureParams {
                    dest_size: Some(Vec2::new(dim, dim)),
                    ..Default::default()
                }
                },
            );
            draw_rectangle(
                0.0,
                0.0,
                screen_width(),
                screen_height(),
                Color::from_rgba(0, 0, 0, 100),
            );
            draw_rectangle(0.0, 0.0, screen_width(), screen_height(), Color::from_rgba(0, 0, 0, 100));
        }

        self.widget_back.draw(assets).await;


@@ 69,7 66,6 @@ impl GameComponent for MapCreatorScreen {
        self.widget_container.draw(assets).await;
    }
    fn ev_loop(&mut self) -> GameEvent {

        {
            // process text input
            if is_key_pressed(KeyCode::Backspace) {


@@ 82,7 78,6 @@ impl GameComponent for MapCreatorScreen {
                }
            }


            if self.name.len() < 10 && is_key_pressed(KeyCode::A) {
                self.name.push('a');
                self.widget_container.set_text(&self.name);


@@ 189,32 184,26 @@ impl GameComponent for MapCreatorScreen {
            }
        }

        match self.widget_create.ev_loop() {
            ButtonEvent::LeftClick => {
                if !self.name.is_empty() && self.name.len() <= 10 {
                    let name = format!("{}.world", self.name);
                    if !World::get_list().contains(&name) {
                        let bscr = BuildScreen::new(&name, true);
                        if let Some(world) = bscr.world {
                            world.to_disk(&name);
                        }
                        return GameEvent::ChangeScreen(Screen::Select(SelectScreen::new()));
        if let ButtonEvent::LeftClick = self.widget_create.ev_loop() {
            if !self.name.is_empty() && self.name.len() <= 10 {
                let name = format!("{}.world", self.name);
                if !World::get_list().contains(&name) {
                    let bscr = BuildScreen::new(&name, true);
                    if let Some(world) = bscr.world {
                        world.to_disk(&name);
                    }
                    return GameEvent::ChangeScreen(Screen::Select(SelectScreen::new()));
                }
            },
            _ => ()
            }
        }

        match self.widget_back.ev_loop() {
            ButtonEvent::LeftClick => {
                return GameEvent::ChangeScreen(Screen::default())
            }
            _ => ()
        if let ButtonEvent::LeftClick = self.widget_back.ev_loop() {
            return GameEvent::ChangeScreen(Screen::default());
        }

        // return to welcome screen
        if is_key_pressed(KeyCode::Escape) {
            return GameEvent::ChangeScreen(Screen::default())
            return GameEvent::ChangeScreen(Screen::default());
        }

        GameEvent::None

M src/screens/map_select.rs => src/screens/map_select.rs +53 -74
@@ 1,21 1,8 @@
use macroquad::prelude::*;
use crate::game::{
    GameComponent,
    GameEvent,
    world::World
};
use super::{build::BuildScreen, map_creator::MapCreatorScreen, Screen};
use crate::game::{world::World, GameComponent, GameEvent};
use crate::textures::AssetStore;
use crate::ui::{
    TextButton,
    Widget,
    ButtonEvent,
    SelectableText
};
use super::{
    Screen,
    build::BuildScreen,
    map_creator::MapCreatorScreen
};
use crate::ui::{ButtonEvent, SelectableText, TextButton, Widget};
use macroquad::prelude::*;

/// The map select screen
/// also allows creation of new maps


@@ 40,7 27,7 @@ pub struct SelectScreen {
    /// currently selected slot
    selected: Option<String>,
    /// currently visible page
    page: usize
    page: usize,
}
impl SelectScreen {
    /// create a new `MapSelectorScreen` instance


@@ 52,12 39,9 @@ impl SelectScreen {
                .with_font_size(50)
                .with_font_color(LIGHTGRAY),
            #[cfg(feature = "multiplayer")]
            widget_host: TextButton::new("Host", 0.35, 0.8)
                .with_font_size(30),
            widget_play: TextButton::new("Play", 0.65, 0.8)
                .with_font_size(30),
            widget_new: TextButton::new("New", 0.5, 0.8)
                .with_font_size(30),
            widget_host: TextButton::new("Host", 0.35, 0.8).with_font_size(30),
            widget_play: TextButton::new("Play", 0.65, 0.8).with_font_size(30),
            widget_new: TextButton::new("New", 0.5, 0.8).with_font_size(30),
            level_select_widgets: vec![
                SelectableText::new(levels.get(0).unwrap_or(&"???".to_string()), 0.5, 0.4)
                    .with_visibility(!levels.is_empty()),


@@ 67,7 51,7 @@ impl SelectScreen {
                    .with_visibility(levels.len() >= 3),
            ],
            selected: None,
            page: 0
            page: 0,
        }
    }
}


@@ 76,19 60,27 @@ impl GameComponent for SelectScreen {
        {
            // draw background image
            let dim: f32 = if screen_width() < screen_height() {
                screen_width() * 2.0/3.0
            } else { screen_height() * 2.0/3.0 };
                screen_width() * 2.0 / 3.0
            } else {
                screen_height() * 2.0 / 3.0
            };
            draw_texture_ex(
                assets.icon,
                screen_width()/2.0 - dim/2.0,
                screen_height()/2.0 - dim/2.0,
                screen_width() / 2.0 - dim / 2.0,
                screen_height() / 2.0 - dim / 2.0,
                WHITE,
                DrawTextureParams {
                    dest_size: Some(Vec2::new(dim, dim)),
                    ..Default::default()
                }
                },
            );
            draw_rectangle(
                0.0,
                0.0,
                screen_width(),
                screen_height(),
                Color::from_rgba(0, 0, 0, 100),
            );
            draw_rectangle(0.0, 0.0, screen_width(), screen_height(), Color::from_rgba(0, 0, 0, 100));
        }

        self.widget_back.draw(assets).await;


@@ 103,63 95,48 @@ impl GameComponent for SelectScreen {
        self.widget_play.draw(assets).await;
    }
    fn ev_loop(&mut self) -> GameEvent {
        match self.widget_back.ev_loop() {
            ButtonEvent::LeftClick => {
                // return to welcome screen
                return GameEvent::ChangeScreen(Screen::default())
            },
            _ => ()
        if let ButtonEvent::LeftClick = self.widget_back.ev_loop() {
            // return to welcome screen
            return GameEvent::ChangeScreen(Screen::default());
        }

        #[cfg(feature = "multiplayer")]
        {
            match self.widget_host.ev_loop() {
                ButtonEvent::LeftClick => {
                    if let Some(file_name) = &self.selected {
                        if let Some(scr) = BuildScreen::from_disk(file_name) {
                            return GameEvent::ChangeScreen(Screen::Build(scr.as_host()));
                        }
            if let ButtonEvent::LeftClick = self.widget_host.ev_loop() {
                if let Some(file_name) = &self.selected {
                    if let Some(scr) = BuildScreen::from_disk(file_name) {
                        return GameEvent::ChangeScreen(Screen::Build(scr.with_host_mode()));
                    }
                }
                _ => ()
            }
        }
        match self.widget_new.ev_loop() {
            ButtonEvent::LeftClick => {
                return GameEvent::ChangeScreen(Screen::Create(MapCreatorScreen::new()))
            },
            _ => ()
        if let ButtonEvent::LeftClick = self.widget_new.ev_loop() {
            return GameEvent::ChangeScreen(Screen::Create(MapCreatorScreen::new()));
        }
        match self.widget_play.ev_loop() {
            ButtonEvent::LeftClick => {
                if let Some(file_name) = &self.selected {
                    if let Some(scr) = BuildScreen::from_disk(file_name) {
                        return GameEvent::ChangeScreen(Screen::Build(scr));
                    }
        if let ButtonEvent::LeftClick = self.widget_play.ev_loop() {
            if let Some(file_name) = &self.selected {
                if let Some(scr) = BuildScreen::from_disk(file_name) {
                    return GameEvent::ChangeScreen(Screen::Build(scr));
                }
            }
            _ => ()
        }

        for slot in &mut self.level_select_widgets {
            slot.set_selected(false);
            match slot.ev_loop() {
                ButtonEvent::LeftClick => {
                    if let Some(sel) = &self.selected && sel == &slot.get_text() {
                        // already selected
                        // deselect
                        self.selected = None;
                    } else {
                        // user selected this item
                        self.selected = Some(slot.get_text().to_string());
                    }
                },
                _ => ()
            if let ButtonEvent::LeftClick = slot.ev_loop() {
                if let Some(sel) = &self.selected && sel == slot.get_text() {
                    // already selected
                    // deselect
                    self.selected = None;
                } else {
                    // user selected this item
                    self.selected = Some(slot.get_text().to_string());
                }
            }
        }
        if let Some(selection) = &self.selected {
            for slot in &mut self.level_select_widgets {
                if &slot.get_text() == selection {
                if slot.get_text() == selection {
                    slot.set_selected(true);
                }
            }


@@ 167,15 144,18 @@ impl GameComponent for SelectScreen {

        // return to welcome screen
        if is_key_pressed(KeyCode::Q) || is_key_pressed(KeyCode::Escape) {
            return GameEvent::ChangeScreen(Screen::default())
            return GameEvent::ChangeScreen(Screen::default());
        }

        if is_key_pressed(KeyCode::Down) && self.list.len() >= self.level_select_widgets.len() && self.page < self.list.len() - self.level_select_widgets.len() {
        if is_key_pressed(KeyCode::Down)
            && self.list.len() >= self.level_select_widgets.len()
            && self.page < self.list.len() - self.level_select_widgets.len()
        {
            self.page += 1;

            // update widget text
            for (i, slot) in self.level_select_widgets.iter_mut().enumerate() {
                if let Some(text) = self.list.get(i+self.page) {
                if let Some(text) = self.list.get(i + self.page) {
                    slot.set_visibility(true);
                    slot.set_text(text);
                } else {


@@ 186,10 166,9 @@ impl GameComponent for SelectScreen {
        if is_key_pressed(KeyCode::Up) && self.page > 0 {
            self.page -= 1;


            // update widget text
            for (i, slot) in self.level_select_widgets.iter_mut().enumerate() {
                if let Some(text) = self.list.get(i+self.page) {
                if let Some(text) = self.list.get(i + self.page) {
                    slot.set_visibility(true);
                    slot.set_text(text);
                } else {

M src/screens/mod.rs => src/screens/mod.rs +6 -9
@@ 1,17 1,14 @@
mod welcome;
pub mod build;
mod map_select;
mod map_creator;
mod map_select;
mod welcome;

use welcome::WelcomeScreen;
use crate::game::{GameComponent, GameEvent};
use crate::textures::AssetStore;
use build::BuildScreen;
use map_select::SelectScreen;
use map_creator::MapCreatorScreen;
use crate::textures::AssetStore;
use crate::game::{
    GameEvent,
    GameComponent
};
use map_select::SelectScreen;
use welcome::WelcomeScreen;

/// Possible screens
/// that can be shown

M src/screens/welcome.rs => src/screens/welcome.rs +29 -38
@@ 1,19 1,11 @@
use macroquad::prelude::*;
use crate::game::{
    GameComponent,
    GameEvent
};
use crate::textures::AssetStore;
use crate::ui::{
    TextButton,
    CenteredText,
    Widget,
    ButtonEvent
};
use super::map_select::SelectScreen;
#[cfg(feature = "multiplayer")]
use super::build::BuildScreen;
use super::map_select::SelectScreen;
use super::Screen;
use crate::game::{GameComponent, GameEvent};
use crate::textures::AssetStore;
use crate::ui::{ButtonEvent, CenteredText, TextButton, Widget};
use macroquad::prelude::*;

/// The welcome screen
pub struct WelcomeScreen {


@@ 32,40 24,42 @@ impl WelcomeScreen {
    /// create a new `WelcomeScreen` instance
    pub fn new() -> Self {
        Self {
            widget_title: CenteredText::new("LittleTown", 0.5,0.45)
                .with_font_size(100),
            widget_select: TextButton::new("Maps", 0.35, 0.55)
                .with_font_size(40),
            widget_title: CenteredText::new("LittleTown", 0.5, 0.45).with_font_size(100),
            widget_select: TextButton::new("Maps", 0.35, 0.55).with_font_size(40),
            #[cfg(feature = "multiplayer")]
            widget_join: TextButton::new("Join", 0.5, 0.55)
                .with_font_size(40),
            widget_quit: TextButton::new("Quit", 0.65, 0.55)
                .with_font_size(40)
            widget_join: TextButton::new("Join", 0.5, 0.55).with_font_size(40),
            widget_quit: TextButton::new("Quit", 0.65, 0.55).with_font_size(40),
        }
    }
}
impl GameComponent for WelcomeScreen {
    async fn draw(&self, assets: &AssetStore) {

        {
            // draw background image
            let dim: f32 = if screen_width() < screen_height() {
                screen_width() * 2.0/3.0
            } else { screen_height() * 2.0/3.0 };
                screen_width() * 2.0 / 3.0
            } else {
                screen_height() * 2.0 / 3.0
            };
            draw_texture_ex(
                assets.icon,
                screen_width()/2.0 - dim/2.0,
                screen_height()/2.0 - dim/2.0,
                screen_width() / 2.0 - dim / 2.0,
                screen_height() / 2.0 - dim / 2.0,
                WHITE,
                DrawTextureParams {
                    dest_size: Some(Vec2::new(dim, dim)),
                    ..Default::default()
                }
                },
            );
            draw_rectangle(
                0.0,
                0.0,
                screen_width(),
                screen_height(),
                Color::from_rgba(0, 0, 0, 100),
            );
            draw_rectangle(0.0, 0.0, screen_width(), screen_height(), Color::from_rgba(0, 0, 0, 100));
        }


        self.widget_title.draw(assets).await;

        self.widget_select.draw(assets).await;


@@ 76,18 70,15 @@ impl GameComponent for WelcomeScreen {
    fn ev_loop(&mut self) -> GameEvent {
        self.widget_title.ev_loop();

        match self.widget_select.ev_loop() {
            ButtonEvent::LeftClick => return GameEvent::ChangeScreen(Screen::Select(SelectScreen::new())),
            _ => ()
        if let ButtonEvent::LeftClick = self.widget_select.ev_loop() {
            return GameEvent::ChangeScreen(Screen::Select(SelectScreen::new()));
        }
        #[cfg(feature = "multiplayer")]
        match self.widget_join.ev_loop() {
            ButtonEvent::LeftClick => return GameEvent::ChangeScreen(Screen::Build(BuildScreen::join())),
            _ => ()
        if let ButtonEvent::LeftClick = self.widget_join.ev_loop() {
            return GameEvent::ChangeScreen(Screen::Build(BuildScreen::join()));
        }
        match self.widget_quit.ev_loop() {
            ButtonEvent::LeftClick => return GameEvent::Quit,
            _ => ()
        if let ButtonEvent::LeftClick = self.widget_quit.ev_loop() {
            return GameEvent::Quit;
        }

        GameEvent::None

M src/textures.rs => src/textures.rs +33 -30
@@ 1,5 1,5 @@
use macroquad::prelude::*;
use crate::game::types::Direction;
use macroquad::prelude::*;

/// Collection of textures required by game
pub struct AssetStore {


@@ 13,7 13,7 @@ pub struct UIAssetCollection {
    pub panel_brown: (Texture2D, Texture2D),
    pub arrow_left: Texture2D,
    pub arrow_right: Texture2D,
    pub close_button: Texture2D
    pub close_button: Texture2D,
}
#[derive(Clone)]
pub struct DirectionalTexture {


@@ 28,7 28,7 @@ impl DirectionalTexture {
            Direction::North => self.north,
            Direction::South => self.south,
            Direction::East => self.east,
            Direction::West => self.west
            Direction::West => self.west,
        }
    }
}


@@ 36,24 36,20 @@ impl DirectionalTexture {
/// macro used to include png assets
macro_rules! include_img_asset {
    ($name:literal) => {
        Texture2D::from_image(
            &Image::from_file_with_format(
                include_bytes!(concat!("../assets/", $name)),
                Some(ImageFormat::Png)
            )
        )
        Texture2D::from_image(&Image::from_file_with_format(
            include_bytes!(concat!("../assets/", $name)),
            Some(ImageFormat::Png),
        ))
    };
}

/// macro used to include .png tiles from an asset pack
macro_rules! include_tile {
    ($name:literal, $dir:literal) => {
        Texture2D::from_image(
            &Image::from_file_with_format(
                include_bytes!(concat!("../../assets/", $name, "_", $dir, ".png")),
                Some(ImageFormat::Png)
            )
        )
        Texture2D::from_image(&Image::from_file_with_format(
            include_bytes!(concat!("../../assets/", $name, "_", $dir, ".png")),
            Some(ImageFormat::Png),
        ))
    };
    ($name:literal) => {
        DirectionalTexture {


@@ 62,19 58,17 @@ macro_rules! include_tile {
            east: include_tile!($name, "E"),
            west: include_tile!($name, "W"),
        }
    }
    };
}

/// macro used to include ui elements
/// from the ui asset pack
macro_rules! include_ui_img {
    ($name:literal) => {
        Texture2D::from_image(
            &Image::from_file_with_format(
                include_bytes!(concat!("../assets/ui/PNG/", $name, ".png")),
                Some(ImageFormat::Png)
            )
        )
        Texture2D::from_image(&Image::from_file_with_format(
            include_bytes!(concat!("../assets/ui/PNG/", $name, ".png")),
            Some(ImageFormat::Png),
        ))
    };
}



@@ 83,17 77,26 @@ impl AssetStore {
    pub async fn init() -> Self {
        Self {
            icon: include_img_asset!("icon.png"),
            font: load_ttf_font_from_bytes(include_bytes!("../assets/fonts/Fonts/Kenney Pixel.ttf")).expect("Failed to load font"),
            font: load_ttf_font_from_bytes(include_bytes!(
                "../assets/fonts/Fonts/Kenney Pixel.ttf"
            ))
            .expect("Failed to load font"),
            ui: UIAssetCollection {
                long_button: (include_ui_img!("buttonLong_brown"),
                              include_ui_img!("buttonLong_brown_pressed")),
                panel_blue: (include_ui_img!("panel_blue"),
                             include_ui_img!("panelInset_blue")),
                panel_brown: (include_ui_img!("panel_brown"),
                              include_ui_img!("panelInset_brown")),
                long_button: (
                    include_ui_img!("buttonLong_brown"),
                    include_ui_img!("buttonLong_brown_pressed"),
                ),
                panel_blue: (
                    include_ui_img!("panel_blue"),
                    include_ui_img!("panelInset_blue"),
                ),
                panel_brown: (
                    include_ui_img!("panel_brown"),
                    include_ui_img!("panelInset_brown"),
                ),
                arrow_left: include_ui_img!("arrowBrown_left"),
                arrow_right: include_ui_img!("arrowBrown_right"),
                close_button: include_ui_img!("iconCross_brown")
                close_button: include_ui_img!("iconCross_brown"),
            },
        }
    }

M src/ui/centered_text.rs => src/ui/centered_text.rs +12 -13
@@ 1,6 1,6 @@
use macroquad::prelude::*;
use crate::textures::AssetStore;
use super::Widget;
use crate::textures::AssetStore;
use macroquad::prelude::*;

/// UI Widget
/// used to draw centered text


@@ 21,7 21,7 @@ pub struct CenteredText {
    font: Option<Font>,
    /// text color
    /// defaults to white
    color: Option<Color>
    color: Option<Color>,
}
impl CenteredText {
    /// create new centered-text widget


@@ 32,7 32,7 @@ impl CenteredText {
            text: text.to_string(),
            font_size: None,
            font: None,
            color: None
            color: None,
        }
    }
    /// sets the font size


@@ 68,7 68,7 @@ impl CenteredText {
impl Widget for CenteredText {
    type Event = bool;
    async fn draw(&self, _assets: &AssetStore) {
        let (x,y) = (screen_width() * self.x, screen_height() * self.y);
        let (x, y) = (screen_width() * self.x, screen_height() * self.y);

        let mut title_params = TextParams {
            font_size: self.font_size.unwrap_or(60),


@@ 83,22 83,21 @@ impl Widget for CenteredText {
            self.font,
            self.font_size.unwrap_or(60),
            1.0,
            0.0);
            0.0,
        );
        draw_text_ex(
            &self.text,
            x - title_center.x,
            y- title_center.y,
            title_params);
            y - title_center.y,
            title_params,
        );
    }
    fn ev_loop(&mut self) -> Self::Event {
        true
    }
    fn get_dimensions(&self) -> (f32, f32) {
        let text_dim: TextDimensions = measure_text(
            &self.text,
            self.font,
            self.font_size.unwrap_or(60),
            1.0);
        let text_dim: TextDimensions =
            measure_text(&self.text, self.font, self.font_size.unwrap_or(60), 1.0);
        (text_dim.width, text_dim.height)
    }
}

M src/ui/item_frame.rs => src/ui/item_frame.rs +41 -34
@@ 1,8 1,8 @@
use macroquad::prelude::*;
use crate::textures::AssetStore;
use super::{ButtonEvent, CenteredText, Widget};
use crate::game::blocks::Block;
use crate::game::types::Direction;
use super::{Widget, ButtonEvent, CenteredText};
use crate::textures::AssetStore;
use macroquad::prelude::*;

pub struct ItemFrame {
    /// middle of button (x axis)


@@ 24,12 24,11 @@ pub struct ItemFrame {
    /// widget used to display count
    widget_amount: CenteredText,
    /// the direciton the block is facing
    direction: Direction
    direction: Direction,
}
impl ItemFrame {
    type Event = ButtonEvent;
    /// creates a new widget
    pub fn new(x: f32, y:f32, size: f32) -> Self {
    pub fn new(x: f32, y: f32, size: f32) -> Self {
        Self {
            x,
            y,


@@ 38,8 37,7 @@ impl ItemFrame {
            selected: false,
            block: None,
            direction: Direction::North,
            widget_amount: CenteredText::new("", x, y)
                .with_font_size(30)
            widget_amount: CenteredText::new("", x, y).with_font_size(30),
        }
    }
    /// adds a block to the item frame


@@ 83,7 81,12 @@ impl ItemFrame {
    }
    /// update block, direction and amount in one go
    #[allow(dead_code)]
    pub fn update(&mut self, selected: &Option<Block>, direction: &Direction, amount: &Option<usize>) {
    pub fn update(
        &mut self,
        selected: &Option<Block>,
        direction: &Direction,
        amount: &Option<usize>,
    ) {
        if let Some(block) = selected {
            self.block = Some(block.clone());
            self.amount = Some(0);


@@ 103,11 106,15 @@ impl ItemFrame {
impl Widget for ItemFrame {
    type Event = ButtonEvent;
    fn get_dimensions(&self) -> (f32, f32) {
        let smallest = if screen_width() < screen_height() { screen_width() } else { screen_height() };
        let smallest = if screen_width() < screen_height() {
            screen_width()
        } else {
            screen_height()
        };
        (self.size * smallest, self.size * smallest)
    }
    async fn draw(&self, assets: &AssetStore) {
        let (x,y) = (screen_width() * self.x, screen_height() * self.y);
        let (x, y) = (screen_width() * self.x, screen_height() * self.y);
        let (size, _) = self.get_dimensions();

        let mut bg = assets.ui.panel_brown.0;


@@ 119,47 126,44 @@ impl Widget for ItemFrame {
        // draw background
        draw_texture_ex(
            bg,
            x - size/2.0,
            y - size/2.0,
            x - size / 2.0,
            y - size / 2.0,
            WHITE,
            DrawTextureParams {
                dest_size: Some(Vec2::new(size, size)),
                ..Default::default()
            }
            },
        );

        // draw brown foreground
        {
            let scale = size/100.0;
            let scale = size / 100.0;
            let dim = scale * 93.0;
            draw_texture_ex(
                assets.ui.panel_brown.1,
                x - dim/2.0,
                y - dim/2.0,
                x - dim / 2.0,
                y - dim / 2.0,
                WHITE,
                DrawTextureParams {
                    dest_size: Some(Vec2::new(dim, dim)),
                    ..Default::default()
                }
                },
            );
        }

        if let Some(block) = &self.block {
            // draw block
            let width = size * 2.0/3.0;
            let width = size * 2.0 / 3.0;
            let height = (width * 352.0) / 256.0;
            draw_texture_ex(
                block.get_texture().await.get_dir(&self.direction),
                x - width / 2.0,
                y - (height * 238.0)/352.0,

                y - (height * 238.0) / 352.0,
                WHITE,
                DrawTextureParams {
                    dest_size: Some(Vec2::new(
                        width,
                        height)),
                    dest_size: Some(Vec2::new(width, height)),
                    ..Default::default()
                }
                },
            );
            // draw amount
            self.widget_amount.draw(assets).await;


@@ 169,17 173,20 @@ impl Widget for ItemFrame {
        let (width, height) = self.get_dimensions();

        let (mx, my) = mouse_position();
        let (x,y) = (screen_width() * self.x, screen_height() * self.y);
        let (x, y) = (screen_width() * self.x, screen_height() * self.y);

        if mx >= x - width/2.0 && mx <= x + width/2.0
            && my >= y - height/2.0 && my <= y + height/2.0 {
                if is_mouse_button_pressed(MouseButton::Left) {
                    return Self::Event::LeftClick;
                }
                if is_mouse_button_pressed(MouseButton::Right) {
                    return Self::Event::RightClick;
                }
        if mx >= x - width / 2.0
            && mx <= x + width / 2.0
            && my >= y - height / 2.0
            && my <= y + height / 2.0
        {
            if is_mouse_button_pressed(MouseButton::Left) {
                return Self::Event::LeftClick;
            }
            if is_mouse_button_pressed(MouseButton::Right) {
                return Self::Event::RightClick;
            }
        }

        Self::Event::None
    }

M src/ui/mod.rs => src/ui/mod.rs +2 -2
@@ 1,5 1,5 @@
use macroquad::prelude::*;
use crate::textures::AssetStore;
use macroquad::prelude::*;

mod centered_text;
mod item_frame;


@@ 19,7 19,7 @@ pub enum ButtonEvent {
    /// user clicked on button with left mouse button
    LeftClick,
    /// user clicked on button with right mouse button
    RightClick
    RightClick,
}

/// A UI item that can be rendered

M src/ui/selectable_text.rs => src/ui/selectable_text.rs +27 -27
@@ 1,6 1,6 @@
use macroquad::prelude::*;
use super::{ButtonEvent, Widget};
use crate::textures::AssetStore;
use super::{Widget, ButtonEvent};
use macroquad::prelude::*;

/// A widget similar to a button,
/// but it can be selected


@@ 24,7 24,7 @@ pub struct SelectableText {
    /// true if widget option is selected
    selected: bool,
    /// if the widget is visible or not
    visible: bool
    visible: bool,
}
impl SelectableText {
    /// creates a new text button widget


@@ 37,7 37,7 @@ impl SelectableText {
            font_color: None,
            font_size: None,
            selected: false,
            visible: true
            visible: true,
        }
    }
    /// set the visibility


@@ 96,19 96,16 @@ impl SelectableText {
impl Widget for SelectableText {
    type Event = ButtonEvent;
    fn get_dimensions(&self) -> (f32, f32) {
        let text_dim: TextDimensions = measure_text(
            &self.text,
            self.font,
            self.font_size.unwrap_or(60),
            1.0);
        (text_dim.width + 30.0, text_dim.height+20.0)
        let text_dim: TextDimensions =
            measure_text(&self.text, self.font, self.font_size.unwrap_or(60), 1.0);
        (text_dim.width + 30.0, text_dim.height + 20.0)
    }
    async fn draw(&self, assets: &AssetStore) {
        if !self.visible {
            return;
        }

        let (x,y) = (screen_width() * self.x, screen_height() * self.y);
        let (x, y) = (screen_width() * self.x, screen_height() * self.y);
        let (width, height) = self.get_dimensions();

        let mut bg = assets.ui.panel_brown.0;


@@ 119,13 116,13 @@ impl Widget for SelectableText {
        // draw background
        draw_texture_ex(
            bg,
            x - width/2.0,
            y - height/2.0,
            x - width / 2.0,
            y - height / 2.0,
            WHITE,
            DrawTextureParams {
                dest_size: Some(Vec2::new(width, height)),
                ..Default::default()
            }
            },
        );

        let text_center = get_text_center(


@@ 133,7 130,8 @@ impl Widget for SelectableText {
            self.font,
            self.font_size.unwrap_or(60),
            1.0,
            0.0);
            0.0,
        );
        draw_text_ex(
            &self.text,
            x - text_center.x,


@@ 142,7 140,8 @@ impl Widget for SelectableText {
                font_size: self.font_size.unwrap_or(60),
                color: self.font_color.unwrap_or(WHITE),
                ..Default::default()
            });
            },
        );
    }
    fn ev_loop(&mut self) -> Self::Event {
        if !self.visible {


@@ 152,20 151,21 @@ impl Widget for SelectableText {
        let (width, height) = self.get_dimensions();

        let (mx, my) = mouse_position();
        let (x,y) = (screen_width() * self.x, screen_height() * self.y);
        let (x, y) = (screen_width() * self.x, screen_height() * self.y);

        if mx >= x - width/2.0 && mx <= x + width/2.0
            && my >= y - height/2.0 && my <= y + height/2.0 {
                if is_mouse_button_pressed(MouseButton::Left) {
                    return Self::Event::LeftClick;
                }
                if is_mouse_button_pressed(MouseButton::Right) {
                    return Self::Event::RightClick;
                }
        if mx >= x - width / 2.0
            && mx <= x + width / 2.0
            && my >= y - height / 2.0
            && my <= y + height / 2.0
        {
            if is_mouse_button_pressed(MouseButton::Left) {
                return Self::Event::LeftClick;
            }

            if is_mouse_button_pressed(MouseButton::Right) {
                return Self::Event::RightClick;
            }
        }

        Self::Event::None

    }
}

M src/ui/text_button.rs => src/ui/text_button.rs +33 -27
@@ 1,6 1,6 @@
use macroquad::prelude::*;
use super::{ButtonEvent, Widget};
use crate::textures::AssetStore;
use super::{Widget, ButtonEvent};
use macroquad::prelude::*;

/// UI Widget
/// a clickable button with text on it


@@ 20,7 20,7 @@ pub struct TextButton {
    /// custom font size to use
    font_size: Option<u16>,
    /// true if mouse is over button
    hovered: bool
    hovered: bool,
}
impl TextButton {
    /// creates a new text button widget


@@ 32,7 32,7 @@ impl TextButton {
            font: None,
            font_color: None,
            font_size: None,
            hovered: false
            hovered: false,
        }
    }
    /// sets the custom font


@@ 68,19 68,20 @@ impl TextButton {
impl Widget for TextButton {
    type Event = ButtonEvent;
    fn get_dimensions(&self) -> (f32, f32) {
        let text_dim: TextDimensions = measure_text(
            &self.text,
            self.font,
            self.font_size.unwrap_or(60),
            1.0);
        (text_dim.width + 30.0, text_dim.height+20.0)
        let text_dim: TextDimensions =
            measure_text(&self.text, self.font, self.font_size.unwrap_or(60), 1.0);
        (text_dim.width + 30.0, text_dim.height + 20.0)
    }
    async fn draw(&self, assets: &AssetStore) {
        let (x,y) = (screen_width() * self.x, screen_height() * self.y);
        let (x, y) = (screen_width() * self.x, screen_height() * self.y);
        let (width, height) = self.get_dimensions();

        draw_texture_ex(
            if self.hovered { assets.ui.long_button.1 } else { assets.ui.long_button.0 },
            if self.hovered {
                assets.ui.long_button.1
            } else {
                assets.ui.long_button.0
            },
            x - width / 2.0,
            y - height / 2.0,
            WHITE,


@@ 90,15 91,17 @@ impl Widget for TextButton {
                rotation: 0.0,
                flip_x: false,
                flip_y: false,
                pivot: None
            });
                pivot: None,
            },
        );

        let text_center = get_text_center(
            &self.text,
            self.font,
            self.font_size.unwrap_or(60),
            1.0,
            0.0);
            0.0,
        );
        draw_text_ex(
            &self.text,
            x - text_center.x,


@@ 107,7 110,8 @@ impl Widget for TextButton {
                font_size: self.font_size.unwrap_or(60),
                color: self.font_color.unwrap_or(WHITE),
                ..Default::default()
            });
            },
        );
    }
    fn ev_loop(&mut self) -> Self::Event {
        self.hovered = false;


@@ 115,20 119,22 @@ impl Widget for TextButton {
        let (width, height) = self.get_dimensions();

        let (mx, my) = mouse_position();
        let (x,y) = (screen_width() * self.x, screen_height() * self.y);
        let (x, y) = (screen_width() * self.x, screen_height() * self.y);

        if mx >= x - width/2.0 && mx <= x + width/2.0
            && my >= y - height/2.0 && my <= y + height/2.0 {
                self.hovered = true;
        if mx >= x - width / 2.0
            && mx <= x + width / 2.0
            && my >= y - height / 2.0
            && my <= y + height / 2.0
        {
            self.hovered = true;

                if is_mouse_button_pressed(MouseButton::Left) {
                    return Self::Event::LeftClick;
                }
                if is_mouse_button_pressed(MouseButton::Right) {
                    return Self::Event::RightClick;
                }
            if is_mouse_button_pressed(MouseButton::Left) {
                return Self::Event::LeftClick;
            }

            if is_mouse_button_pressed(MouseButton::Right) {
                return Self::Event::RightClick;
            }
        }

        Self::Event::None
    }