Formatted code using cargo clippy and cargo fmt
19 files changed, 1382 insertions(+), 1019 deletions(-) M src/game/blocks.rs M src/game/camera.rs M src/game/inventory.rs M src/game/mod.rs M src/game/types.rs M src/game/world.rs M src/main.rs M src/p2p.rs M src/screens/build.rs M src/screens/map_creator.rs M src/screens/map_select.rs M src/screens/mod.rs M src/screens/welcome.rs M src/textures.rs M src/ui/centered_text.rs M src/ui/item_frame.rs M src/ui/mod.rs M src/ui/selectable_text.rs M src/ui/text_button.rs
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 == ¤t).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 }