Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chocolate Bar + Multiple Waves + Game Mode Arguments + Small Refactors #42

Merged
merged 12 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,27 @@ Available Enemies:

If not set manually, random enemies will be selected.

### Arguments for game modes

- In Native:
- Arguments are parsed from the "mode" command line argument.
```
mythmallow --game --mode "survival --wave 2"
```
- In WebAssembly:
- Arguments are parsed from the "mode" query parameter.
```
https://mythmallow.io/?game&mode=|survival?wave=2|
```

#### Survival mode

##### \-\-wave \<WAVE>

Specifies the wave when starting the application in-game.

If not set manually, or set incorrectly, the first wave will be selected.

## Documentation

### API Documentation
Expand Down
164 changes: 164 additions & 0 deletions enemies/sweet/src/chocolate_bar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use {
crate::prelude::*,
mythmallow::{
enemy::constants::RANGED_ENEMY_TAG,
prelude::*,
},
};

/// Size of the enemy.
pub const SIZE: f32 = 15.00;

/// Color of the enemy.
pub const COLOR: Color = Color::Rgba { red: 0.329, green: 0.149, blue: 0.031, alpha: 1.000 };

/// Health of the enemy.
pub const HEALTH: Health = Health(5.00);

/// Speed of the enemy.
pub const SPEED: Speed = Speed(80.00);

/// Damage of the enemy.
pub const DAMAGE: Damage = Damage(3.00);

/// Attack cooldown of the enemy.
pub const ATTACK_COOLDOWN: Duration = Duration::from_millis(1500);

/// Size of the projectiles of the enemy.
pub const PROJECTILE_SIZE: f32 = 7.50;

/// Color of the projectiles of the enemy.
pub const PROJECTILE_COLOR: Color =
Color::Rgba { red: 0.229, green: 0.099, blue: 0.031, alpha: 1.000 };

/// Speed of the projectiles of the enemy.
pub const PROJECTILE_SPEED: f32 = 200.00;

/// Component for the enemy "Chocolate Bar".
#[derive(Clone, Component, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct ChocolateBar;

impl IEnemy for ChocolateBar {
fn id(&self) -> SmolStr {
"chocolate-bar".into()
}

fn name(&self) -> SmolStr {
"Chocolate Bar".into()
}

fn health(&self) -> Health {
HEALTH
}

fn speed(&self) -> Speed {
SPEED
}

fn collider(&self) -> Collider {
Collider::ball(SIZE)
}

fn spawn(&self, world: &mut World, position: Position) {
world.run_system_once_with((self.clone(), position), spawn);
}
}

/// Plugin for managing the enemy "Chocolate Bar".
pub struct ChocolateBarPlugin;

impl Plugin for ChocolateBarPlugin {
fn build(&self, app: &mut App) {
// Register the enemy.
let mut enemy_registry = app.world.resource_mut::<EnemyRegistry>();
enemy_registry.register(SweetEnemyPack, ChocolateBar).add_tag(RANGED_ENEMY_TAG);

// Register components.
app.register_type::<ChocolateBar>();

// Add systems.
app.add_systems(Update, follow_player::<ChocolateBar>.in_set(GameplaySystems::Enemy));
app.add_systems(Update, attack.in_set(GameplaySystems::Enemy));
}
}

/// Spawns the enemy.
pub fn spawn(
In((enemy, position)): In<(ChocolateBar, Position)>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut counter: ResMut<EnemyCounter>,
) {
let mesh = MaterialMesh2dBundle {
mesh: meshes.add(shape::Circle::new(SIZE).into()).into(),
material: materials.add(ColorMaterial::from(COLOR)),
transform: Transform::from_translation(position.extend(Depth::Enemy.z())),
..default()
};

EnemyBundle::builder()
.enemy(enemy)
.position(position)
.mesh(mesh)
.build()
.spawn(&mut commands, &mut counter)
.insert((IdealDistanceToPlayer(100.00), Cooldown::<Attack>::new(ATTACK_COOLDOWN / 2)));
}

/// Attacks to the player.
pub fn attack(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
enemy_query: Query<(Entity, &Transform), (With<ChocolateBar>, Without<Cooldown<Attack>>)>,
player_query: Query<&Transform, (With<Player>, Without<ChocolateBar>)>,
spatial_query: SpatialQuery,
) {
let player_transform = match player_query.get_single() {
Ok(query_result) => query_result,
Err(_) => {
return;
},
};
for (enemy_entity, enemy_transform) in enemy_query.iter() {
let enemy_position = Position::new(enemy_transform.translation.xy());

let to_player = (player_transform.translation - enemy_transform.translation).xy();
let player_distance = to_player.length();
let player_direction = to_player.normalize();

let obstacle_between_enemy_and_player = utils::map::find_obstacle(
&spatial_query,
&enemy_position,
&player_direction,
player_distance,
);
if obstacle_between_enemy_and_player.is_some() {
continue;
}

let projectile_entity = ProjectileBundle::builder()
.mesh(MaterialMesh2dBundle {
mesh: meshes.add(shape::Circle::new(PROJECTILE_SIZE).into()).into(),
material: materials.add(ColorMaterial::from(PROJECTILE_COLOR)),
transform: Transform::from_translation(
enemy_position.extend(Depth::Projectile.z()),
),
..default()
})
.collider(Collider::ball(PROJECTILE_SIZE))
.position(enemy_position)
.velocity(LinearVelocity(player_direction * PROJECTILE_SPEED))
.damage(DAMAGE)
.build()
.spawn_toward_player(&mut commands)
.id();

commands
.entity(enemy_entity)
.add_child(projectile_entity)
.insert(Cooldown::<Attack>::new(ATTACK_COOLDOWN));
}
}
8 changes: 4 additions & 4 deletions enemies/sweet/src/gummy_bear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ use {
pub const SIZE: f32 = 15.00;

/// Health of the enemy.
pub const HEALTH: f32 = 5.00;
pub const HEALTH: Health = Health(5.00);

/// Speed of the enemy.
pub const SPEED: f32 = 100.00;
pub const SPEED: Speed = Speed(100.00);

/// Contact damage of the enemy.
pub const CONTACT_DAMAGE: f32 = 3.00;
Expand All @@ -40,11 +40,11 @@ impl IEnemy for GummyBear {
}

fn health(&self) -> Health {
Health(HEALTH)
HEALTH
}

fn speed(&self) -> Speed {
Speed(SPEED)
SPEED
}

fn collider(&self) -> Collider {
Expand Down
1 change: 1 addition & 0 deletions enemies/sweet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ pub mod pack;
pub mod plugin;
pub mod prelude;

pub mod chocolate_bar;
pub mod gummy_bear;
6 changes: 5 additions & 1 deletion enemies/sweet/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use {
crate::gummy_bear::GummyBearPlugin,
crate::{
chocolate_bar::ChocolateBarPlugin,
gummy_bear::GummyBearPlugin,
},
mythmallow::prelude::*,
};

Expand All @@ -9,6 +12,7 @@ pub struct SweetEnemiesPlugin;
impl Plugin for SweetEnemiesPlugin {
fn build(&self, app: &mut App) {
// Add sub-plugins.
app.add_plugins(ChocolateBarPlugin);
app.add_plugins(GummyBearPlugin);
}
}
1 change: 1 addition & 0 deletions enemies/sweet/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub use crate::{
chocolate_bar::ChocolateBar,
gummy_bear::GummyBear,
pack::SweetEnemyPack,
plugin::SweetEnemiesPlugin,
Expand Down
79 changes: 57 additions & 22 deletions game/src/configuration/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl Args {
pub fn parse() -> Args {
#[derive(Parser)]
#[command(about, version)]
#[clap(name = "mythmallow")]
struct ArgsParser {
#[arg(long)]
pub configuration: Option<PathBuf>,
Expand Down Expand Up @@ -78,10 +79,10 @@ impl Args {
impl Display for ArgsParser {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(data) = &self.data {
write!(f, " --data {:?}", data)?;
write!(f, " --data \"{}\"", data.display())?;
}
if let Some(configuration) = &self.configuration {
write!(f, " --configuration {:?}", configuration)?;
write!(f, " --configuration \"{}\"", configuration.display())?;
}
if let Some(seed) = &self.seed {
write!(f, " --seed {}", seed)?;
Expand All @@ -90,22 +91,24 @@ impl Args {
write!(f, " --game")?;
}
if let Some(mode) = &self.mode {
write!(f, " --mode {:?}", mode)?;
write!(f, " --mode \"{}\"", mode)?;
}
if let Some(player) = &self.player {
write!(f, " --player {:?}", player)?;
write!(f, " --player \"{}\"", player)?;
}
if let Some(enemies) = &self.enemies {
write!(f, " --enemies {:?}", enemies)?;
write!(f, " --enemies \"{}\"", enemies)?;
}
Ok(())
}
}

impl ArgsParser {
pub fn canonicalize(self) -> Args {
log::info!("version: v{}", env!("CARGO_PKG_VERSION"));
log::info!("args:{}", self);
let args = format!("{}", self);
if !args.is_empty() {
log::info!("args:\n\n{}\n", args.trim());
}

let configuration_directory = self
.configuration
Expand All @@ -128,7 +131,15 @@ impl Args {
}
});

log::info!("configuration directory: {:?}", configuration_directory);
let configuration_directory_display =
format!("{}", configuration_directory.display());
log::info!(
"configuration directory:\n\n{}\n",
configuration_directory_display
.trim_start_matches("\\\\?\\")
.trim_start_matches("\"\\\\?\\")
.trim_end_matches('"'),
);

let data_directory = self
.data
Expand All @@ -149,7 +160,14 @@ impl Args {
}
});

log::info!("data directory: {:?}", data_directory);
let data_directory_display = format!("{}", data_directory.display());
log::info!(
"data directory:\n\n{}\n",
data_directory_display
.trim_start_matches("\\\\?\\")
.trim_start_matches("\"\\\\?\\")
.trim_end_matches('"'),
);

let seed = self.seed;
let start_in_game = self.game;
Expand All @@ -169,6 +187,11 @@ impl Args {
}
}

log::info!("version:\n\nv{}\n", env!("CARGO_PKG_VERSION"));

let help = format!("{}", <ArgsParser as CommandFactory>::command().render_help());
log::info!("usage:\n\n{}\n", help.trim());

#[cfg(feature = "native")]
{
ArgsParser::parse().canonicalize()
Expand All @@ -182,21 +205,33 @@ impl Args {
if query.is_empty() {
ArgsParser::default().canonicalize()
} else {
ArgsParser::try_parse_from(
std::iter::once("mythmallow".to_owned()).chain(
query
.trim_start_matches('?')
.split('&')
.flat_map(|option| {
let mut option = option.split('=');
log::warn!("{:#?}", query);
let args = query.replace(['?', '&'], " --");

let key = format!("--{}", option.next().unwrap());
let value = option.fold(String::new(), std::ops::Add::add);
let mut parsed_args = vec![];
let mut parsed_arg = String::new();

[key, value]
})
.filter(|arg| !arg.is_empty()),
),
let mut block = false;
for char in args.trim_start().chars() {
match char {
'|' => {
block = !block;
},
' ' | '=' if !block => {
parsed_args.push(std::mem::take(&mut parsed_arg));
},
_ => {
parsed_arg.push(char);
},
}
}
if !parsed_arg.is_empty() {
parsed_args.push(parsed_arg);
}
log::warn!("{:#?}", parsed_args);

ArgsParser::try_parse_from(
std::iter::once("mythmallow".to_owned()).chain(parsed_args),
)
.unwrap_or_else(|error| {
let full_error = format!("{}", error);
Expand Down
Loading
Loading