Merge branch '27-watering-crops' into 'dev'
Implement watering crops See merge request softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik!24
This commit is contained in:
@@ -49,3 +49,5 @@ cargo run
|
|||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
This project is released under the terms of the [MIT License](LICENSE).
|
This project is released under the terms of the [MIT License](LICENSE).
|
||||||
|
|
||||||
|
The font used is called Jersey 10. It is protected under the [SIL OPEN FONT LICENSE Version 1.1](assets/fonts/Jersey10.LICENSE).
|
||||||
|
|||||||
Binary file not shown.
BIN
assets/fonts/Jersey10-Regular.ttf
Normal file
BIN
assets/fonts/Jersey10-Regular.ttf
Normal file
Binary file not shown.
93
assets/fonts/Jersey10.LICENSE
Normal file
93
assets/fonts/Jersey10.LICENSE
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2023 The Soft Type Project Authors (https://github.com/scfried/soft-type-jersey)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -10,6 +10,9 @@ pub struct Tile {
|
|||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct CropVisual;
|
pub struct CropVisual;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct WaterVisual;
|
||||||
|
|
||||||
#[derive(Component, Default, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Component, Default, Serialize, Deserialize, Clone, Debug)]
|
||||||
pub enum TileState {
|
pub enum TileState {
|
||||||
#[default]
|
#[default]
|
||||||
@@ -17,6 +20,7 @@ pub enum TileState {
|
|||||||
Empty,
|
Empty,
|
||||||
Occupied {
|
Occupied {
|
||||||
seed: ItemType,
|
seed: ItemType,
|
||||||
|
watered: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use components::CropVisual;
|
use components::{CropVisual, WaterVisual};
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
@@ -51,10 +51,21 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, config: Res<Gam
|
|||||||
aseprite: asset_server.load("crop.aseprite"),
|
aseprite: asset_server.load("crop.aseprite"),
|
||||||
},
|
},
|
||||||
Sprite::default(),
|
Sprite::default(),
|
||||||
Transform::default(),
|
Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)),
|
||||||
Visibility::Hidden,
|
Visibility::Hidden,
|
||||||
ZIndex(1),
|
ZIndex(1),
|
||||||
));
|
));
|
||||||
|
parent.spawn((
|
||||||
|
WaterVisual,
|
||||||
|
AseSlice {
|
||||||
|
name: "Water".into(),
|
||||||
|
aseprite: asset_server.load("crop.aseprite"),
|
||||||
|
},
|
||||||
|
Sprite::default(),
|
||||||
|
Transform::from_translation(Vec3::new(0.0, 0.0, 2.0)),
|
||||||
|
Visibility::Hidden,
|
||||||
|
ZIndex(2),
|
||||||
|
));
|
||||||
})
|
})
|
||||||
.id();
|
.id();
|
||||||
column.push(tile_entity);
|
column.push(tile_entity);
|
||||||
@@ -78,7 +89,8 @@ fn cleanup(mut commands: Commands, tile_query: Query<Entity, With<Tile>>) {
|
|||||||
|
|
||||||
fn update_tiles(
|
fn update_tiles(
|
||||||
mut query: Query<(&TileState, &mut AseSlice, &Children)>,
|
mut query: Query<(&TileState, &mut AseSlice, &Children)>,
|
||||||
mut crop_query: Query<&mut Visibility, With<CropVisual>>,
|
mut crop_query: Query<&mut Visibility, (With<CropVisual>, Without<WaterVisual>)>,
|
||||||
|
mut water_query: Query<&mut Visibility, (With<WaterVisual>, Without<CropVisual>)>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
for (state, mut slice, children) in &mut query {
|
for (state, mut slice, children) in &mut query {
|
||||||
@@ -102,6 +114,12 @@ fn update_tiles(
|
|||||||
_ => Visibility::Hidden,
|
_ => Visibility::Hidden,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if let Ok(mut visibility) = water_query.get_mut(child) {
|
||||||
|
*visibility = match state {
|
||||||
|
TileState::Occupied { watered: true, .. } => Visibility::Visible,
|
||||||
|
_ => Visibility::Hidden,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ fn debug_click(
|
|||||||
seed: ItemType::BerrySeed {
|
seed: ItemType::BerrySeed {
|
||||||
name: "Debug".into(),
|
name: "Debug".into(),
|
||||||
},
|
},
|
||||||
|
watered: false,
|
||||||
},
|
},
|
||||||
TileState::Occupied { .. } => TileState::Unclaimed,
|
TileState::Occupied { .. } => TileState::Unclaimed,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -157,8 +157,27 @@ fn handle_continue(
|
|||||||
mut phase_res: ResMut<CurrentPhase>,
|
mut phase_res: ResMut<CurrentPhase>,
|
||||||
mut session_tracker: ResMut<SessionTracker>,
|
mut session_tracker: ResMut<SessionTracker>,
|
||||||
settings: Res<TimerSettings>,
|
settings: Res<TimerSettings>,
|
||||||
|
mut tile_query: Query<&mut TileState>,
|
||||||
) {
|
) {
|
||||||
for _ in messages.read() {
|
for _ in messages.read() {
|
||||||
|
let entering_break = if let Phase::Finished { completed_phase } = &phase_res.0 {
|
||||||
|
matches!(**completed_phase, Phase::Focus { .. })
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
next_phase(&mut phase_res, &mut session_tracker, &settings);
|
next_phase(&mut phase_res, &mut session_tracker, &settings);
|
||||||
|
|
||||||
|
if entering_break {
|
||||||
|
println!("Resetting watered state for all crops.");
|
||||||
|
for mut state in tile_query.iter_mut() {
|
||||||
|
if let TileState::Occupied { seed, .. } = &*state {
|
||||||
|
*state = TileState::Occupied {
|
||||||
|
seed: seed.clone(),
|
||||||
|
watered: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ use crate::prelude::*;
|
|||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum InteractionAction {
|
pub enum InteractionAction {
|
||||||
Plant(ItemType),
|
Plant(ItemType),
|
||||||
|
Water,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InteractionAction {
|
impl InteractionAction {
|
||||||
pub fn get_name(&self, game_config: &GameConfig) -> String {
|
pub fn get_name(&self, game_config: &GameConfig) -> String {
|
||||||
match self {
|
match self {
|
||||||
InteractionAction::Plant(item) => format!("Pflanze {}", item.singular(game_config)),
|
InteractionAction::Plant(item) => format!("Pflanze {}", item.singular(game_config)),
|
||||||
|
InteractionAction::Water => "Gießen".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ impl InteractionAction {
|
|||||||
) -> Option<AseSlice> {
|
) -> Option<AseSlice> {
|
||||||
match self {
|
match self {
|
||||||
InteractionAction::Plant(item) => Some(item.get_sprite(asset_server, game_config)),
|
InteractionAction::Plant(item) => Some(item.get_sprite(asset_server, game_config)),
|
||||||
|
InteractionAction::Water => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,23 +32,30 @@ impl InteractionAction {
|
|||||||
) -> Vec<InteractionAction> {
|
) -> Vec<InteractionAction> {
|
||||||
let mut options: Vec<InteractionAction> = vec![];
|
let mut options: Vec<InteractionAction> = vec![];
|
||||||
|
|
||||||
for &entity in &inventory.items {
|
match tile_state {
|
||||||
let Ok(stack) = item_query.get(entity) else {
|
TileState::Occupied { watered, .. } => {
|
||||||
continue;
|
if !*watered {
|
||||||
};
|
options.push(InteractionAction::Water);
|
||||||
if stack.amount <= 0 {
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
TileState::Empty => {
|
||||||
match tile_state {
|
for &entity in &inventory.items {
|
||||||
TileState::Empty => match &stack.item_type {
|
let Ok(stack) = item_query.get(entity) else {
|
||||||
ItemType::BerrySeed { .. } => {
|
continue;
|
||||||
options.push(InteractionAction::Plant(stack.item_type.clone()));
|
};
|
||||||
|
if stack.amount <= 0 {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
},
|
match &stack.item_type {
|
||||||
_ => (),
|
ItemType::BerrySeed { .. } => {
|
||||||
|
options.push(InteractionAction::Plant(stack.item_type.clone()));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
options
|
options
|
||||||
@@ -81,6 +91,7 @@ impl InteractionAction {
|
|||||||
println!("Planting {:?}", seed_type);
|
println!("Planting {:?}", seed_type);
|
||||||
*tile_state = TileState::Occupied {
|
*tile_state = TileState::Occupied {
|
||||||
seed: seed_type.clone(),
|
seed: seed_type.clone(),
|
||||||
|
watered: false,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
println!("No {:?} in inventory!", seed_type);
|
println!("No {:?} in inventory!", seed_type);
|
||||||
@@ -89,6 +100,17 @@ impl InteractionAction {
|
|||||||
println!("Tile is not empty, cannot plant.");
|
println!("Tile is not empty, cannot plant.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
InteractionAction::Water => {
|
||||||
|
if let TileState::Occupied { seed, .. } = &*tile_state {
|
||||||
|
println!("Watering {:?}", seed);
|
||||||
|
*tile_state = TileState::Occupied {
|
||||||
|
seed: seed.clone(),
|
||||||
|
watered: true,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
println!("Tile is not occupied, cannot water.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,5 +36,14 @@ fn main() {
|
|||||||
features::ShopPlugin,
|
features::ShopPlugin,
|
||||||
))
|
))
|
||||||
.insert_resource(config)
|
.insert_resource(config)
|
||||||
|
.add_systems(Startup, overwrite_default_font)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn overwrite_default_font(mut fonts: ResMut<Assets<Font>>) {
|
||||||
|
let custom_font_bytes = include_bytes!("../assets/fonts/Jersey10-Regular.ttf");
|
||||||
|
let custom_font =
|
||||||
|
Font::try_from_bytes(custom_font_bytes.to_vec()).expect("Failed to parse custom font");
|
||||||
|
let default_font_id = Handle::<Font>::default().id();
|
||||||
|
let _ = fonts.insert(default_font_id, custom_font);
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ fn test_plant_seed_interaction() {
|
|||||||
let tile_entity = grid.get_tile((1, 1)).unwrap();
|
let tile_entity = grid.get_tile((1, 1)).unwrap();
|
||||||
let tile_state = app.world().entity(tile_entity).get::<TileState>().unwrap();
|
let tile_state = app.world().entity(tile_entity).get::<TileState>().unwrap();
|
||||||
|
|
||||||
if let TileState::Occupied { seed } = tile_state {
|
if let TileState::Occupied { seed, .. } = tile_state {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*seed,
|
*seed,
|
||||||
ItemType::BerrySeed {
|
ItemType::BerrySeed {
|
||||||
@@ -164,3 +164,89 @@ fn test_plant_seed_no_inventory() {
|
|||||||
panic!("Tile should remain Empty, found {:?}", tile_state);
|
panic!("Tile should remain Empty, found {:?}", tile_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_water_crop() {
|
||||||
|
let seed_type = ItemType::BerrySeed {
|
||||||
|
name: "TestSeed".into(),
|
||||||
|
};
|
||||||
|
let mut app = setup_interaction_app(
|
||||||
|
3,
|
||||||
|
3,
|
||||||
|
&[(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
TileState::Occupied {
|
||||||
|
seed: seed_type.clone(),
|
||||||
|
watered: false,
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify Water option is available
|
||||||
|
let _ = app.world_mut().run_system_once(
|
||||||
|
move |grid: Res<Grid>,
|
||||||
|
tile_query: Query<&TileState>,
|
||||||
|
inventory: Res<Inventory>,
|
||||||
|
item_query: Query<&ItemStack>| {
|
||||||
|
let tile_entity = grid.get_tile((1, 1)).unwrap();
|
||||||
|
let tile_state = tile_query.get(tile_entity).unwrap();
|
||||||
|
let options = InteractionAction::list_options(tile_state, &inventory, item_query);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
options.contains(&InteractionAction::Water),
|
||||||
|
"Water option should be available"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute Water
|
||||||
|
let _ = app.world_mut().run_system_once(
|
||||||
|
move |grid: Res<Grid>,
|
||||||
|
mut tile_query: Query<&mut TileState>,
|
||||||
|
mut inventory: ResMut<Inventory>,
|
||||||
|
mut item_stack_query: Query<&mut ItemStack>,
|
||||||
|
mut commands: Commands| {
|
||||||
|
let action = InteractionAction::Water;
|
||||||
|
action.execute(
|
||||||
|
(1, 1),
|
||||||
|
&grid,
|
||||||
|
&mut tile_query,
|
||||||
|
&mut inventory,
|
||||||
|
&mut item_stack_query,
|
||||||
|
&mut commands,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
// Assert Tile State Watered
|
||||||
|
let grid = app.world().resource::<Grid>();
|
||||||
|
let tile_entity = grid.get_tile((1, 1)).unwrap();
|
||||||
|
let tile_state = app.world().entity(tile_entity).get::<TileState>().unwrap();
|
||||||
|
|
||||||
|
if let TileState::Occupied { watered, .. } = tile_state {
|
||||||
|
assert!(watered, "Tile should be watered");
|
||||||
|
} else {
|
||||||
|
panic!("Tile should be Occupied, found {:?}", tile_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Water option is NOT available
|
||||||
|
let _ = app.world_mut().run_system_once(
|
||||||
|
move |grid: Res<Grid>,
|
||||||
|
tile_query: Query<&TileState>,
|
||||||
|
inventory: Res<Inventory>,
|
||||||
|
item_query: Query<&ItemStack>| {
|
||||||
|
let tile_entity = grid.get_tile((1, 1)).unwrap();
|
||||||
|
let tile_state = tile_query.get(tile_entity).unwrap();
|
||||||
|
let options = InteractionAction::list_options(tile_state, &inventory, item_query);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!options.contains(&InteractionAction::Water),
|
||||||
|
"Water option should NOT be available"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,8 +91,9 @@ fn test_find_path_simple() {
|
|||||||
fn test_find_path_around_obstacle() {
|
fn test_find_path_around_obstacle() {
|
||||||
let obstacle: TileState = TileState::Occupied {
|
let obstacle: TileState = TileState::Occupied {
|
||||||
seed: ItemType::BerrySeed {
|
seed: ItemType::BerrySeed {
|
||||||
name: "test".into(),
|
name: "Test".into(),
|
||||||
},
|
},
|
||||||
|
watered: false,
|
||||||
};
|
};
|
||||||
let obstacles = vec![
|
let obstacles = vec![
|
||||||
(2, 2, obstacle.clone()),
|
(2, 2, obstacle.clone()),
|
||||||
@@ -142,8 +143,9 @@ fn test_find_path_around_obstacle() {
|
|||||||
fn test_find_path_no_path() {
|
fn test_find_path_no_path() {
|
||||||
let obstacle: TileState = TileState::Occupied {
|
let obstacle: TileState = TileState::Occupied {
|
||||||
seed: ItemType::BerrySeed {
|
seed: ItemType::BerrySeed {
|
||||||
name: "test".into(),
|
name: "Test".into(),
|
||||||
},
|
},
|
||||||
|
watered: false,
|
||||||
};
|
};
|
||||||
let obstacles = vec![
|
let obstacles = vec![
|
||||||
(2, 0, obstacle.clone()),
|
(2, 0, obstacle.clone()),
|
||||||
|
|||||||
Reference in New Issue
Block a user