Macos builds working

Includes fmod (dynamic linking) and pretty dmg installer
main
Elijah Voigt 1 year ago committed by Elijah Voigt
parent 061ba38092
commit 90584babb6

@ -14,14 +14,12 @@ rustflags = [
# `brew install llvm` # `brew install llvm`
[target.x86_64-apple-darwin] [target.x86_64-apple-darwin]
rustflags = [ rustflags = [
"-Clink-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld", # Use LLD Linker
"-Zshare-generics=y", # (Nightly) Make the current crate share its generic instantiations "-Zshare-generics=y", # (Nightly) Make the current crate share its generic instantiations
"-Zthreads=0", # (Nightly) Use improved multithreading with the recommended amount of threads. "-Zthreads=0", # (Nightly) Use improved multithreading with the recommended amount of threads.
] ]
[target.aarch64-apple-darwin] [target.aarch64-apple-darwin]
rustflags = [ rustflags = [
"-Clink-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld", # Use LLD Linker
"-Zshare-generics=y", # (Nightly) Make the current crate share its generic instantiations "-Zshare-generics=y", # (Nightly) Make the current crate share its generic instantiations
"-Zthreads=0", # (Nightly) Use improved multithreading with the recommended amount of threads. "-Zthreads=0", # (Nightly) Use improved multithreading with the recommended amount of threads.
] ]

4
.gitattributes vendored

@ -1,5 +1,3 @@
fmodstudio/linux filter=lfs diff=lfs merge=lfs -text
fmodstudio/windows filter=lfs diff=lfs merge=lfs -text
assets/ filter=lfs diff=lfs merge=lfs -text assets/ filter=lfs diff=lfs merge=lfs -text
*.so filter=lfs diff=lfs merge=lfs -text *.so filter=lfs diff=lfs merge=lfs -text
*.so.* filter=lfs diff=lfs merge=lfs -text *.so.* filter=lfs diff=lfs merge=lfs -text
@ -18,3 +16,5 @@ assets/ filter=lfs diff=lfs merge=lfs -text
*.lib filter=lfs diff=lfs merge=lfs -text *.lib filter=lfs diff=lfs merge=lfs -text
*.gltf filter=lfs diff=lfs merge=lfs -text *.gltf filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text *.mp4 filter=lfs diff=lfs merge=lfs -text
*.dylib filter=lfs diff=lfs merge=lfs -text
*.dylib filter=lfs diff=lfs merge=lfs -text

9
.gitignore vendored

@ -30,3 +30,12 @@ temp/
*.zip *.zip
*.tar.gz *.tar.gz
.DS_Store
*.dmg
!platforms/macos/Martian\ Chess.app/Contents/Info.plist
platforms/macos
icon_*.png
*.iconset
*.icns

3
Cargo.lock generated

@ -655,8 +655,7 @@ dependencies = [
[[package]] [[package]]
name = "bevy_fmod" name = "bevy_fmod"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/Salzian/bevy_fmod.git?branch=main#bba24000626d251b1e1582d8b3863caa6dc701a6"
checksum = "52cb5dfc9e27a6b8be8cabb6925ba1731e2a7f02bdc0bc774ba3b6b652613f46"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bevy", "bevy",

@ -1,11 +1,12 @@
[package] [package]
name = "martian-chess" name = "martian-chess"
description = "Martian Chess videogame"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
build = "build.rs" build = "build.rs"
[dependencies] [dependencies]
bevy_fmod = { version = "0.4" } bevy_fmod = { git = "https://github.com/Salzian/bevy_fmod.git", branch = "main" }
bevy = { version = "0.13", features = ["jpeg", "hdr", "serialize"] } bevy = { version = "0.13", features = ["jpeg", "hdr", "serialize"] }
serde = "1" serde = "1"
toml = { version = "0.8", features = ["parse"] } toml = { version = "0.8", features = ["parse"] }

@ -2,6 +2,9 @@ fn main() {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
println!("cargo:rustc-link-search=lib/linux"); println!("cargo:rustc-link-search=lib/linux");
#[cfg(target_os = "macos")]
println!("cargo:rustc-link-search=lib/macos");
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
println!("cargo:rustc-link-search=lib/windows"); println!("cargo:rustc-link-search=lib/windows");

BIN
lib/macos/libfmod.dylib (Stored with Git LFS)

Binary file not shown.

BIN
lib/macos/libfmodstudio.dylib (Stored with Git LFS)

Binary file not shown.

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Martian Chess</string>
<key>CFBundleExecutable</key>
<string>MartianChess</string>
<key>CFBundleIconFile</key>
<string>MartianChess.icns</string>
<key>CFBundleIdentifier</key>
<string>your.domain.bevy-game</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>MartianChess</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
</dict>
</plist>

@ -0,0 +1,5 @@
#!/bin/bash
# Generate x86 and arm builds
cargo build --release --target aarch64-apple-darwin
cargo build --release --target x86_64-apple-darwin

@ -0,0 +1,34 @@
#!/bin/bash
# https://gist.github.com/ansarizafar/6fa64f44aa933794c4d6638eec32b9aa
BASE="media/MartianChess.png"
OUT_DIR="media/MartianChess.iconset"
mkdir -p $OUT_DIR
# 1024x1024
sips "$BASE" -Z 1024 -o $OUT_DIR/icon_512x512@2.png
# 512x512
sips "$BASE" -Z 512 -o $OUT_DIR/icon_512x512.png
sips "$BASE" -Z 512 -o $OUT_DIR/icon_256x256@2.png
# 256x256
sips "$BASE" -Z 256 -o $OUT_DIR/icon_256x256.png
sips "$BASE" -Z 256 -o $OUT_DIR/icon_128x128@2.png
# 128x128
sips "$BASE" -Z 128 -o $OUT_DIR/icon_128x128.png
sips "$BASE" -Z 128 -o $OUT_DIR/icon_64x64@2.png
# 64x64
sips "$BASE" -Z 64 -o $OUT_DIR/icon_64x64.png
sips "$BASE" -Z 64 -o $OUT_DIR/icon_32x32@2.png
# 32x32
sips "$BASE" -Z 32 -o $OUT_DIR/icon_32x32.png
sips "$BASE" -Z 32 -o $OUT_DIR/icon_16x16@2.png
# 16x16
sips "$BASE" -Z 16 -o $OUT_DIR/icon_16x16.png
iconutil -c icns $OUT_DIR

@ -0,0 +1,51 @@
#!/bin/bash
# https://github.com/create-dmg/create-dmg/tree/master?tab=readme-ov-file#create-dmg
NAME='Martian Chess.app'
APP="platforms/macos/$NAME"
CONTENTS="$APP/Contents"
GAME="$CONTENTS/MacOS"
RESOURCES="$CONTENTS/Resources"
FRAMEWORKS="$CONTENTS/Frameworks"
mkdir -p "$APP"
mkdir -p "$GAME"
mkdir -p "$RESOURCES"
mkdir -p "$FRAMEWORKS"
# Copy icons to package
cp -f media/MartianChess.icns "$RESOURCES"
# Generate cross-architecture binary
rm -f "$GAME/MartianChess"
lipo "target/x86_64-apple-darwin/release/martian-chess" \
"target/aarch64-apple-darwin/release/martian-chess" \
-create -output "$GAME/MartianChess"
# Copy assets
rm -rf "$GAME/assets"
cp -r assets "$GAME/"
# Copy fmod libraries
cp -f lib/macos/libfmod.dylib "$FRAMEWORKS/"
cp -f lib/macos/libfmodstudio.dylib "$FRAMEWORKS/"
# Update dynamic linking search paths
install_name_tool -change @rpath/libfmod.dylib "@loader_path/../Frameworks/libfmod.dylib" "$GAME/MartianChess"
install_name_tool -change @rpath/libfmodstudio.dylib "@loader_path/../Frameworks/libfmodstudio.dylib" "$GAME/MartianChess"
# Build dmg file
rm -f packages/martian_chess.dmg
create-dmg \
--volname "Martian Chess" \
--volicon "media/MartianChess.icns" \
--window-pos 200 120 \
--window-size 800 400 \
--app-drop-link 600 200 \
--icon-size 100 \
--icon "$NAME" 200 190 \
--hide-extension "$NAME" \
--app-drop-link 600 185 \
"packages/Martian Chess.dmg" \
./platforms/macos/

@ -11,18 +11,17 @@ impl Plugin for AiPlugin {
.add_systems( .add_systems(
Update, Update,
( (
bogo_ai_thinking bogo_ai_thinking.run_if(in_state(AiDrama::Thinking)),
.run_if(in_state(AiDrama::Thinking)), bogo_ai_holding.run_if(in_state(AiDrama::Holding)),
bogo_ai_holding
.run_if(in_state(AiDrama::Holding)),
) )
.run_if(in_state(PlayState::AiBogo)) .run_if(in_state(PlayState::AiBogo))
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
.run_if(in_state(TurnState(Side::A))) .run_if(in_state(TurnState(Side::A)))
.run_if(in_state(TutorialState::None) .run_if(
in_state(TutorialState::None)
.or_else(in_state(TutorialState::Empty)) .or_else(in_state(TutorialState::Empty))
.or_else(in_state(TutorialState::PieceEnd)) .or_else(in_state(TutorialState::PieceEnd)),
) ),
); );
} }
} }
@ -107,15 +106,17 @@ fn bogo_ai_thinking(
// Pick up selected piece // Pick up selected piece
query query
.iter() .iter()
.find_map(|(e, bi)| { .find_map(|(e, bi)| (*bi == from).then_some(e))
(*bi == from).then_some(e)
})
.iter() .iter()
.for_each(|e| { .for_each(|e| {
commands.entity(*e).insert(Selected); commands.entity(*e).insert(Selected);
}); });
*selected = AiMove(Move { from, to: Some(to), ..default() }); *selected = AiMove(Move {
from,
to: Some(to),
..default()
});
// Pass off to next state in the "drama" // Pass off to next state in the "drama"
next_drama.set(AiDrama::Holding); next_drama.set(AiDrama::Holding);

@ -9,14 +9,12 @@ impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<AudioEvent>(); app.add_event::<AudioEvent>();
app.add_plugins(FmodPlugin { app.add_plugins(FmodPlugin::new(&[
audio_banks_paths: &[
"./assets/audio/Martian Chess Audio/Build/Desktop/Master.bank", "./assets/audio/Martian Chess Audio/Build/Desktop/Master.bank",
"./assets/audio/Martian Chess Audio/Build/Desktop/Master.strings.bank", "./assets/audio/Martian Chess Audio/Build/Desktop/Master.strings.bank",
"./assets/audio/Martian Chess Audio/Build/Desktop/Music.bank", "./assets/audio/Martian Chess Audio/Build/Desktop/Music.bank",
"./assets/audio/Martian Chess Audio/Build/Desktop/SFX.bank", "./assets/audio/Martian Chess Audio/Build/Desktop/SFX.bank",
], ]));
});
app.init_resource::<AudioVolume>(); app.init_resource::<AudioVolume>();
app.add_systems(OnEnter(GameState::Intro), play_background); app.add_systems(OnEnter(GameState::Intro), play_background);

@ -121,20 +121,23 @@ fn update_credits(
text.sections = { text.sections = {
credits_text credits_text
.split('\n') .split('\n')
.map(|line| { .map(|line| match line.strip_prefix("# ") {
match line.strip_prefix("# ") {
Some(title) => TextSection { Some(title) => TextSection {
value: format!("{}\n", title), value: format!("{}\n", title),
style: TextStyle { font_size: title_font_size, ..default() }, style: TextStyle {
font_size: title_font_size,
..default()
},
}, },
None => { None => TextSection {
TextSection {
value: format!("{}\n", line), value: format!("{}\n", line),
style: TextStyle { font_size, ..default() }, style: TextStyle {
} font_size,
} ..default()
} },
}).collect() },
})
.collect()
}; };
debug!("Text sections: {:?}", text.sections); debug!("Text sections: {:?}", text.sections);
}); });

@ -31,7 +31,7 @@ impl Plugin for DebugPlugin {
toggle_debug_mode.run_if(on_event::<KeyboardInput>()), toggle_debug_mode.run_if(on_event::<KeyboardInput>()),
display_diagnostics.run_if(in_state(DebugState::Enabled)), display_diagnostics.run_if(in_state(DebugState::Enabled)),
toggle_debug_ui.run_if(state_changed::<DebugState>), toggle_debug_ui.run_if(state_changed::<DebugState>),
aspect_ratio.run_if(on_event::<WindowResized>()) aspect_ratio.run_if(on_event::<WindowResized>()),
), ),
); );
} }
@ -261,10 +261,7 @@ fn debug_piece(
} }
} }
fn aspect_ratio( fn aspect_ratio(mut debug_info: ResMut<DebugInfo>, window: Query<&Window>) {
mut debug_info: ResMut<DebugInfo>,
window: Query<&Window>,
) {
window.iter().for_each(|window| { window.iter().for_each(|window| {
let x = window.resolution.width(); let x = window.resolution.width();
let y = window.resolution.height(); let y = window.resolution.height();

@ -19,18 +19,11 @@ impl Plugin for Display3dPlugin {
color: Color::WHITE, color: Color::WHITE,
brightness: 100.0, brightness: 100.0,
}) })
.insert_resource(PointLightShadowMap { .insert_resource(PointLightShadowMap { size: 512 })
size: 512 .insert_resource(DirectionalLightShadowMap { size: 512 })
})
.insert_resource(DirectionalLightShadowMap {
size: 512
})
.add_systems( .add_systems(
OnExit(GameState::Loading), OnExit(GameState::Loading),
( (fix_skybox, initialize.after(fix_skybox)),
fix_skybox,
initialize.after(fix_skybox),
),
) )
// Systems related to color and camera // Systems related to color and camera
.add_systems( .add_systems(
@ -70,7 +63,7 @@ impl Plugin for Display3dPlugin {
pick_up.run_if(any_component_added::<game::Selected>()), pick_up.run_if(any_component_added::<game::Selected>()),
de_select.run_if(just_pressed(MouseButton::Right)), de_select.run_if(just_pressed(MouseButton::Right)),
put_down.run_if(any_component_removed::<game::Selected>()), put_down.run_if(any_component_removed::<game::Selected>()),
) ),
) )
.add_systems( .add_systems(
Update, Update,
@ -86,8 +79,9 @@ impl Plugin for Display3dPlugin {
switch_sides switch_sides
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
.run_if(state_changed::<game::TurnState>) .run_if(state_changed::<game::TurnState>)
.run_if(any_component_added::<Selected>() .run_if(
.or_else(any_component_removed::<Selected>()) any_component_added::<Selected>()
.or_else(any_component_removed::<Selected>()),
) )
.run_if(should_switch_sides), .run_if(should_switch_sides),
// Camera moving up when first piece is selected in the game // Camera moving up when first piece is selected in the game
@ -106,14 +100,14 @@ impl Plugin for Display3dPlugin {
.or_else(any_component_changed::<BoardIndex>()) .or_else(any_component_changed::<BoardIndex>())
.or_else(any_component_removed::<Animating>()) .or_else(any_component_removed::<Animating>())
.or_else(any_component_removed::<Captured>()) .or_else(any_component_removed::<Captured>())
.or_else(any_component_removed::<Promoted>()) .or_else(any_component_removed::<Promoted>()),
), ),
set_models set_models
.run_if(resource_exists::<tweak::GameTweaks>) .run_if(resource_exists::<tweak::GameTweaks>)
.run_if( .run_if(
any_component_changed::<DisplayModel>() any_component_changed::<DisplayModel>()
.or_else(any_component_added::<DisplayModel>()) .or_else(any_component_added::<DisplayModel>())
.or_else(any_component_removed::<Animating>()) .or_else(any_component_removed::<Animating>()),
), ),
dissolve_animation.run_if(any_with_component::<Dissolving>), dissolve_animation.run_if(any_with_component::<Dissolving>),
capture_piece_start.run_if(any_component_added::<game::BeingCaptured>()), capture_piece_start.run_if(any_component_added::<game::BeingCaptured>()),
@ -121,9 +115,8 @@ impl Plugin for Display3dPlugin {
monitor_animations monitor_animations
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
.run_if(any_component_changed::<AnimationPlayer>()), .run_if(any_component_changed::<AnimationPlayer>()),
set_animation_player_speed set_animation_player_speed.run_if(
.run_if(any_component_added::<Animating>() any_component_added::<Animating>().or_else(resource_changed::<AnimationSpeed>),
.or_else(resource_changed::<AnimationSpeed>)
), ),
set_animation_speed set_animation_speed
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
@ -132,8 +125,8 @@ impl Plugin for Display3dPlugin {
just_pressed(KeyCode::Enter) just_pressed(KeyCode::Enter)
.or_else(just_pressed(MouseButton::Left)) .or_else(just_pressed(MouseButton::Left))
.or_else(just_released(KeyCode::Enter)) .or_else(just_released(KeyCode::Enter))
.or_else(just_released(MouseButton::Left)) .or_else(just_released(MouseButton::Left)),
) ),
), ),
) )
.add_systems( .add_systems(
@ -157,42 +150,32 @@ impl Plugin for Display3dPlugin {
fog_tweak.run_if(resource_exists::<tweak::GameTweaks>), fog_tweak.run_if(resource_exists::<tweak::GameTweaks>),
bloom_tweak.run_if(resource_exists::<tweak::GameTweaks>), bloom_tweak.run_if(resource_exists::<tweak::GameTweaks>),
set_models.run_if(resource_exists::<tweak::GameTweaks>), set_models.run_if(resource_exists::<tweak::GameTweaks>),
update_pieces.run_if(resource_exists::<tweak::GameTweaks>).after(set_models), update_pieces
.run_if(resource_exists::<tweak::GameTweaks>)
.after(set_models),
), ),
) )
.add_systems( .add_systems(
Update, Update,
( (setup_dissolve_materials.run_if(any_component_added::<Handle<StandardMaterial>>()),),
setup_dissolve_materials
.run_if(any_component_added::<Handle<StandardMaterial>>()),
)
) )
.add_systems( .add_systems(
OnEnter(GameState::Play), OnEnter(GameState::Play),
( (opening_animation
opening_animation
.run_if(run_once()) .run_if(run_once())
.run_if(in_state(DisplayState::Display3d)), .run_if(in_state(DisplayState::Display3d)),),
),
)
.add_systems(
OnEnter(GameState::Title),
(
fade_title_in,
fixup_shadows
)
) )
.add_systems(OnEnter(GameState::Title), (fade_title_in, fixup_shadows))
.add_systems(OnExit(GameState::Title), fade_title_out) .add_systems(OnExit(GameState::Title), fade_title_out)
.add_systems( .add_systems(
Update, Update,
( (
fade_title fade_title.run_if(any_with_component::<Fading>),
.run_if(any_with_component::<Fading>),
continue_title continue_title
.run_if(in_state(GameState::Title)) .run_if(in_state(GameState::Title))
.run_if(not(any_with_component::<Fading>)) .run_if(not(any_with_component::<Fading>))
.run_if(just_pressed(KeyCode::Enter).or_else(just_pressed(MouseButton::Left))), .run_if(just_pressed(KeyCode::Enter).or_else(just_pressed(MouseButton::Left))),
) ),
); );
} }
} }
@ -303,9 +286,7 @@ fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<Asset
// 3D objects root // 3D objects root
commands commands
.spawn(( .spawn((
SpatialBundle { SpatialBundle { ..default() },
..default()
},
Display3d, Display3d,
DisplayState::Display3d, DisplayState::Display3d,
)) ))
@ -313,8 +294,7 @@ fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<Asset
debug!("Intializing 3D Board!"); debug!("Intializing 3D Board!");
// Board // Board
parent parent.spawn((
.spawn((
Display3d, Display3d,
DisplayState::Display3d, DisplayState::Display3d,
game::BoardComponent, game::BoardComponent,
@ -561,19 +541,30 @@ fn set_models(
tweaks: Res<Assets<Tweaks>>, tweaks: Res<Assets<Tweaks>>,
tweaks_file: Res<tweak::GameTweaks>, tweaks_file: Res<tweak::GameTweaks>,
) { ) {
let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweakfile"); let tweak = tweaks
let assets_handle = tweak.get_handle::<Gltf>("display3d_models_assets_file").unwrap(); .get(tweaks_file.handle.clone())
.expect("Load tweakfile");
let assets_handle = tweak
.get_handle::<Gltf>("display3d_models_assets_file")
.unwrap();
let gltf = gltfs.get(assets_handle).expect("Load GLTF content"); let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
query.iter_mut().for_each(|(entity, mut handle, DisplayModel(key))| { query
.iter_mut()
.for_each(|(entity, mut handle, DisplayModel(key))| {
// Check if any children are animating // Check if any children are animating
if active_animation_players if active_animation_players
.iter_many(children.iter_descendants(entity)) .iter_many(children.iter_descendants(entity))
.count() > 0 { .count()
> 0
{
debug!("Piece {:?} is animating. Skipping...", entity); debug!("Piece {:?} is animating. Skipping...", entity);
} else { } else {
let scene = tweak.get::<String>(key).unwrap(); let scene = tweak.get::<String>(key).unwrap();
let new_handle = gltf.named_scenes.get(scene.as_str()).expect("Game board model"); let new_handle = gltf
.named_scenes
.get(scene.as_str())
.expect("Game board model");
if *new_handle != *handle { if *new_handle != *handle {
debug!("Updating piece for {:?}", entity); debug!("Updating piece for {:?}", entity);
*handle = new_handle.clone(); *handle = new_handle.clone();
@ -694,7 +685,7 @@ fn select(
selected: Query<&BoardIndex, With<game::Selected>>, selected: Query<&BoardIndex, With<game::Selected>>,
state: Res<State<game::TurnState>>, state: Res<State<game::TurnState>>,
mut selections: EventWriter<game::Selection>, mut selections: EventWriter<game::Selection>,
mut moves: EventWriter<Move> mut moves: EventWriter<Move>,
) { ) {
match *piece { match *piece {
// Something is selected, so send an event saying such // Something is selected, so send an event saying such
@ -709,15 +700,13 @@ fn select(
}); });
} }
// Nothing selected, cancel the selection // Nothing selected, cancel the selection
PiecePointer(None) => { PiecePointer(None) => selected.iter().for_each(|board_index| {
selected.iter().for_each(|board_index| {
moves.send(Move { moves.send(Move {
from: *board_index, from: *board_index,
to: Some(*board_index), to: Some(*board_index),
..default() ..default()
}); });
}) }),
},
} }
} }
@ -882,10 +871,7 @@ fn pick_up(
}); });
} }
fn de_select( fn de_select(query: Query<Entity, With<Selected>>, mut commands: Commands) {
query: Query<Entity, With<Selected>>,
mut commands: Commands,
) {
query.iter().for_each(|e| { query.iter().for_each(|e| {
commands.entity(e).remove::<Selected>(); commands.entity(e).remove::<Selected>();
}) })
@ -975,7 +961,8 @@ fn set_animation_speed(
keys: Res<ButtonInput<KeyCode>>, keys: Res<ButtonInput<KeyCode>>,
mouse: Res<ButtonInput<MouseButton>>, mouse: Res<ButtonInput<MouseButton>>,
) { ) {
*animation_speed = if keys.just_pressed(KeyCode::Enter) || mouse.just_pressed(MouseButton::Left) { *animation_speed = if keys.just_pressed(KeyCode::Enter) || mouse.just_pressed(MouseButton::Left)
{
let tweak = tweaks let tweak = tweaks
.get(tweaks_file.handle.clone()) .get(tweaks_file.handle.clone())
.expect("Load tweakfile"); .expect("Load tweakfile");
@ -1003,14 +990,12 @@ fn should_switch_sides(
query: Query<&Side, (With<Camera>, With<Display3d>)>, query: Query<&Side, (With<Camera>, With<Display3d>)>,
state: Res<State<ai::PlayState>>, state: Res<State<ai::PlayState>>,
) -> bool { ) -> bool {
query.iter().all(|side| { query.iter().all(|side| match state.get() {
match state.get() {
ai::PlayState::AiBogo => match side { ai::PlayState::AiBogo => match side {
Side::A => true, Side::A => true,
Side::B => false, Side::B => false,
} },
ai::PlayState::Human => true, ai::PlayState::Human => true,
}
}) })
} }
@ -1034,11 +1019,11 @@ fn switch_sides(
game::TurnState(game::Side::B) => "display3d_models_animations_turn_b", game::TurnState(game::Side::B) => "display3d_models_animations_turn_b",
}; };
let animation_val = tweak.get::<String>(animation_key).unwrap(); let animation_val = tweak.get::<String>(animation_key).unwrap();
let animation = gltf.named_animations.get(animation_val.as_str()).expect("Camera Transition Animation"); let animation = gltf
player.start_with_transition( .named_animations
animation.clone(), .get(animation_val.as_str())
Duration::from_secs_f32(1.00), .expect("Camera Transition Animation");
); player.start_with_transition(animation.clone(), Duration::from_secs_f32(1.00));
*side = state.get().0; *side = state.get().0;
}); });
} }
@ -1049,7 +1034,7 @@ fn vantage_point(
state: Res<State<game::TurnState>>, state: Res<State<game::TurnState>>,
tweaks: Res<Assets<Tweaks>>, tweaks: Res<Assets<Tweaks>>,
tweaks_file: Res<tweak::GameTweaks>, tweaks_file: Res<tweak::GameTweaks>,
mut up: Local<bool> mut up: Local<bool>,
) { ) {
let tweak = tweaks let tweak = tweaks
.get(tweaks_file.handle.clone()) .get(tweaks_file.handle.clone())
@ -1074,11 +1059,11 @@ fn vantage_point(
if animation_key != "" { if animation_key != "" {
let animation_val = tweak.get::<String>(animation_key).unwrap(); let animation_val = tweak.get::<String>(animation_key).unwrap();
let animation = gltf.named_animations.get(animation_val.as_str()).expect("Camera Transition Animation"); let animation = gltf
player.start_with_transition( .named_animations
animation.clone(), .get(animation_val.as_str())
Duration::from_secs_f32(1.00), .expect("Camera Transition Animation");
); player.start_with_transition(animation.clone(), Duration::from_secs_f32(1.00));
} }
}); });
} }
@ -1106,7 +1091,10 @@ impl MaterialExtension for DissolveExtension {
/// Sets up all pieces to have an associated "dissolve" material ready for capture /// Sets up all pieces to have an associated "dissolve" material ready for capture
fn setup_dissolve_materials( fn setup_dissolve_materials(
// All entities with materials are candidates for this procedure // All entities with materials are candidates for this procedure
events: Query<(Entity, &Handle<StandardMaterial>, &Name), (With<Parent>, Added<Handle<StandardMaterial>>)>, events: Query<
(Entity, &Handle<StandardMaterial>, &Name),
(With<Parent>, Added<Handle<StandardMaterial>>),
>,
// Only process newly created pieces (we do not delete pieces at runtime) // Only process newly created pieces (we do not delete pieces at runtime)
query: Query<&Dissolvable>, query: Query<&Dissolvable>,
// Children of pieces are the actual meshes that need materials // Children of pieces are the actual meshes that need materials
@ -1123,10 +1111,7 @@ fn setup_dissolve_materials(
.iter() .iter()
// Handle this entity (mesh) // Handle this entity (mesh)
.for_each(|(child, std_handle, name)| { .for_each(|(child, std_handle, name)| {
if let Some(dissolvable) = query if let Some(dissolvable) = query.iter_many(parents.iter_ancestors(child)).next() {
.iter_many(parents.iter_ancestors(child))
.next() {
debug!("Setting up dissolve material for {:?} {:?}", name, child); debug!("Setting up dissolve material for {:?} {:?}", name, child);
// Extension we will add to existing gltf-sourced materials // Extension we will add to existing gltf-sourced materials
@ -1185,7 +1170,9 @@ fn capture_piece_end(
.entity(entity) .entity(entity)
.insert(Dissolving::In(dissolvable.duration)) .insert(Dissolving::In(dissolvable.duration))
.remove::<BeingCaptured>() .remove::<BeingCaptured>()
.insert(Captured { epoch: board.current_epoch() - 1 }); .insert(Captured {
epoch: board.current_epoch() - 1,
});
} }
}); });
} }
@ -1436,21 +1423,27 @@ fn fade_title(
Fading::In(duration) => { Fading::In(duration) => {
// If we are fully saturated, remove dissolving // If we are fully saturated, remove dissolving
if bgc.0.a() >= 1.0 { if bgc.0.a() >= 1.0 {
commands.entity(e).remove::<Fading>().insert(Visibility::Inherited); commands
.entity(e)
.remove::<Fading>()
.insert(Visibility::Inherited);
} }
// Delta is simple // Delta is simple
time.delta_seconds() / duration time.delta_seconds() / duration
}, }
Fading::Out(duration) => { Fading::Out(duration) => {
// If we are fully saturated, remove dissolving // If we are fully saturated, remove dissolving
if bgc.0.a() <= 0.0 { if bgc.0.a() <= 0.0 {
commands.entity(e).remove::<Fading>().insert(Visibility::Hidden); commands
.entity(e)
.remove::<Fading>()
.insert(Visibility::Hidden);
} }
// Negative delta because we are fading out // Negative delta because we are fading out
-(time.delta_seconds() / duration) -(time.delta_seconds() / duration)
}, }
}; };
// Increment/decrement the alpha value // Increment/decrement the alpha value
let new_a = (bgc.0.a() + step).min(1.0).max(0.0); let new_a = (bgc.0.a() + step).min(1.0).max(0.0);

@ -15,7 +15,7 @@ impl Plugin for GamePlugin {
( (
manage_state_entities::<GameState>().run_if(state_changed::<GameState>), manage_state_entities::<GameState>().run_if(state_changed::<GameState>),
undo_move.run_if(just_pressed(KeyCode::KeyU)), undo_move.run_if(just_pressed(KeyCode::KeyU)),
) ),
) )
.add_systems( .add_systems(
Update, Update,
@ -29,19 +29,19 @@ impl Plugin for GamePlugin {
hide_valid_moves.run_if(any_component_removed::<Selected>()), hide_valid_moves.run_if(any_component_removed::<Selected>()),
manage_score.run_if(any_component_added::<Captured>()), manage_score.run_if(any_component_added::<Captured>()),
check_endgame.run_if(resource_changed::<Board>), check_endgame.run_if(resource_changed::<Board>),
).run_if(in_state(GameState::Play)),
) )
.add_systems(Update, .run_if(in_state(GameState::Play)),
)
.add_systems(
Update,
assert_piece_consistency assert_piece_consistency
.run_if(in_state(GameState::Play)) .run_if(in_state(GameState::Play))
.run_if(resource_changed::<Score>) .run_if(resource_changed::<Score>),
) )
.add_systems(Update, reset_game.run_if(in_state(GameState::Restart))) .add_systems(Update, reset_game.run_if(in_state(GameState::Restart)))
.add_systems(OnEnter(GameState::Endgame), .add_systems(
( OnEnter(GameState::Endgame),
manage_score, (manage_score, set_endgame.after(manage_score)),
set_endgame.after(manage_score),
)
) )
.add_systems(OnExit(GameState::Endgame), clear_endgame) .add_systems(OnExit(GameState::Endgame), clear_endgame)
.add_systems( .add_systems(
@ -132,10 +132,14 @@ impl Piece {
} }
fn moves_at(&self, from: &BoardIndex) -> HashSet<BoardIndex> { fn moves_at(&self, from: &BoardIndex) -> HashSet<BoardIndex> {
self.moves().filter_map(|(x, y)| { self.moves()
.filter_map(|(x, y)| {
let bi = (from.x as isize + x, from.y as isize + y); let bi = (from.x as isize + x, from.y as isize + y);
// Check if this goes out of bounds, if so exclude from the list of possible moves // Check if this goes out of bounds, if so exclude from the list of possible moves
(bi.0 <= 7 && bi.0 >= 0 && bi.1 <= 3 && bi.1 >= 0).then_some(BoardIndex { x: bi.0 as usize, y: bi.1 as usize }) (bi.0 <= 7 && bi.0 >= 0 && bi.1 <= 3 && bi.1 >= 0).then_some(BoardIndex {
x: bi.0 as usize,
y: bi.1 as usize,
})
}) })
.collect() .collect()
} }
@ -434,9 +438,7 @@ impl Board {
Some(from_piece) => { Some(from_piece) => {
let move_type = self.move_type(from, to); let move_type = self.move_type(from, to);
match move_type { match move_type {
MoveType::Invalid => { MoveType::Invalid => Err(GameError::InvalidMove),
Err(GameError::InvalidMove)
},
MoveType::Valid | MoveType::Capture | MoveType::Promotion(..) => { MoveType::Valid | MoveType::Capture | MoveType::Promotion(..) => {
// The current epoch is the last epoch + 1 // The current epoch is the last epoch + 1
let epoch = self.current_epoch(); let epoch = self.current_epoch();
@ -465,18 +467,15 @@ impl Board {
}); });
self.inner[to.y][to.x] = match move_type { self.inner[to.y][to.x] = match move_type {
MoveType::Promotion(_) => { MoveType::Promotion(_) => match (self.at(from), self.at(to)) {
match (self.at(from), self.at(to)) { (Some(Piece::Pawn), Some(Piece::Pawn)) => Some(Piece::Drone),
(Some(Piece::Pawn), Some(Piece::Pawn)) => { (Some(Piece::Pawn), Some(Piece::Drone))
Some(Piece::Drone) | (Some(Piece::Drone), Some(Piece::Pawn)) => Some(Piece::Queen),
}, _ => panic!(
(Some(Piece::Pawn), Some(Piece::Drone)) | (Some(Piece::Drone), Some(Piece::Pawn)) => { "Merges can only happen between pawn+pawn or pawn+drone!"
Some(Piece::Queen) ),
}, },
_ => panic!("Merges can only happen between pawn+pawn or pawn+drone!") _ => Some(*from_piece),
}
},
_ => Some(*from_piece)
}; };
self.inner[from.y][from.x] = None; self.inner[from.y][from.x] = None;
@ -554,11 +553,7 @@ impl Board {
} }
/// Determine given a piece, a to, and a from, what type of move this would be /// Determine given a piece, a to, and a from, what type of move this would be
pub(crate) fn move_type( pub(crate) fn move_type(&self, from: BoardIndex, to: BoardIndex) -> MoveType {
&self,
from: BoardIndex,
to: BoardIndex,
) -> MoveType {
self.line(from, to) self.line(from, to)
.all(|board_index| self.at(board_index).is_none()) .all(|board_index| self.at(board_index).is_none())
.then(|| { .then(|| {
@ -567,8 +562,14 @@ impl Board {
// And the piece is a drone||pawn // And the piece is a drone||pawn
// We can do field promotions // We can do field promotions
let side = Board::side(from).expect("Piece has valid index"); let side = Board::side(from).expect("Piece has valid index");
let side_has_queen = self.on(side).iter().any(|(piece, _)| **piece == Piece::Queen); let side_has_queen = self
let side_has_drone = self.on(side).iter().any(|(piece, _)| **piece == Piece::Drone); .on(side)
.iter()
.any(|(piece, _)| **piece == Piece::Queen);
let side_has_drone = self
.on(side)
.iter()
.any(|(piece, _)| **piece == Piece::Drone);
// Iterate over the piece's moves // Iterate over the piece's moves
piece piece
@ -586,37 +587,29 @@ impl Board {
(Side::A, Side::A) | (Side::B, Side::B) => { (Side::A, Side::A) | (Side::B, Side::B) => {
match dest_at { match dest_at {
// Cannot move on top of a friendly // Cannot move on top of a friendly
Some(to_piece) => { Some(to_piece) => match (piece, to_piece) {
match (piece, to_piece) { (Piece::Pawn, Piece::Pawn) => (!side_has_drone)
(Piece::Pawn, Piece::Pawn) => { .then_some(MoveType::Promotion(Piece::Drone)),
(!side_has_drone).then_some(MoveType::Promotion(Piece::Drone)) (Piece::Drone, Piece::Pawn)
} | (Piece::Pawn, Piece::Drone) => (!side_has_queen)
(Piece::Drone, Piece::Pawn) | (Piece::Pawn, Piece::Drone) => { .then_some(MoveType::Promotion(Piece::Queen)),
(!side_has_queen).then_some(MoveType::Promotion(Piece::Queen)) _ => Some(MoveType::Invalid),
},
_ => {
Some(MoveType::Invalid)
}
}
}, },
// Any other spot is valid // Any other spot is valid
None => { None => Some(MoveType::Valid),
Some(MoveType::Valid)
}
} }
} }
// Check for moving across the canal // Check for moving across the canal
(Side::A, Side::B) | (Side::B, Side::A) => { (Side::A, Side::B) | (Side::B, Side::A) => {
match dest_at { match dest_at {
Some(_) => { Some(_) => Some(MoveType::Capture),
Some(MoveType::Capture)
}
None => { None => {
debug!("Last move: {:?}", self.moves.last()); debug!("Last move: {:?}", self.moves.last());
// move is valid if it does not un-do the previous move for this piece // move is valid if it does not un-do the previous move for this piece
match self.moves.last() { match self.moves.last() {
Some(previous) => { Some(previous) => {
let is_undo = previous.from == to && previous.to == Some(from); let is_undo = previous.from == to
&& previous.to == Some(from);
(!is_undo).then_some(MoveType::Valid) (!is_undo).then_some(MoveType::Valid)
} }
// First move in the game, this is valid (and impossible) // First move in the game, this is valid (and impossible)
@ -631,20 +624,27 @@ impl Board {
} }
}) })
}) })
}).flatten().flatten().or(Some(MoveType::Invalid)).unwrap() })
.flatten()
.flatten()
.or(Some(MoveType::Invalid))
.unwrap()
} }
/// Returns the possible moves the piece at this tile can make. /// Returns the possible moves the piece at this tile can make.
pub(crate) fn valid_moves(&self, current_board_index: BoardIndex) -> HashSet<BoardIndex> { pub(crate) fn valid_moves(&self, current_board_index: BoardIndex) -> HashSet<BoardIndex> {
if let Some(piece) = self.at(current_board_index) { if let Some(piece) = self.at(current_board_index) {
piece.moves_at(&current_board_index).iter().filter_map(|move_index| { piece
.moves_at(&current_board_index)
.iter()
.filter_map(|move_index| {
// Get the move type (or none if totally invalid) // Get the move type (or none if totally invalid)
let result = self.move_type(current_board_index, *move_index); let result = self.move_type(current_board_index, *move_index);
match result { match result {
MoveType::Invalid => None, MoveType::Invalid => None,
MoveType::Capture | MoveType::Promotion(..) | MoveType::Valid => { MoveType::Capture | MoveType::Promotion(..) | MoveType::Valid => {
Some(*move_index) Some(*move_index)
}, }
} }
}) })
.collect() .collect()
@ -730,7 +730,11 @@ mod test {
(7, 2).into(), (7, 2).into(),
]); ]);
assert_eq!(expected, Piece::Queen.moves_at(&(4, 2).into()), "Generated queen moves"); assert_eq!(
expected,
Piece::Queen.moves_at(&(4, 2).into()),
"Generated queen moves"
);
assert_eq!(expected, given, "Basic queen moves"); assert_eq!(expected, given, "Basic queen moves");
} }
@ -851,7 +855,8 @@ mod test {
// Pawn cannot merge with queen // Pawn cannot merge with queen
{ {
let given = board.valid_moves((2, 1).into()); let given = board.valid_moves((2, 1).into());
let expected: HashSet<BoardIndex> = HashSet::from([(3, 0).into(), (3, 2).into(), (1, 2).into()]); let expected: HashSet<BoardIndex> =
HashSet::from([(3, 0).into(), (3, 2).into(), (1, 2).into()]);
assert_eq!(expected, given, "Pawn cannot merge with queen"); assert_eq!(expected, given, "Pawn cannot merge with queen");
} }
} }
@ -870,8 +875,7 @@ mod test {
{ {
let given = board.valid_moves((4, 1).into()); let given = board.valid_moves((4, 1).into());
let expected: HashSet<BoardIndex> = let expected: HashSet<BoardIndex> = HashSet::from([
HashSet::from([
(1, 1).into(), (1, 1).into(),
(2, 1).into(), (2, 1).into(),
(2, 3).into(), (2, 3).into(),
@ -908,20 +912,13 @@ mod test {
{ {
let given = board.valid_moves((1, 0).into()); let given = board.valid_moves((1, 0).into());
let expected: HashSet<BoardIndex> = let expected: HashSet<BoardIndex> = HashSet::from([(2, 1).into()]);
HashSet::from([
(2, 1).into()
]);
assert_eq!(expected, given); assert_eq!(expected, given);
} }
{ {
let given = board.valid_moves((2, 0).into()); let given = board.valid_moves((2, 0).into());
let expected: HashSet<BoardIndex> = let expected: HashSet<BoardIndex> = HashSet::from([(2, 1).into(), (2, 2).into()]);
HashSet::from([
(2, 1).into(),
(2, 2).into(),
]);
assert_eq!(expected, given); assert_eq!(expected, given);
} }
} }
@ -950,7 +947,6 @@ mod test {
qqd....."#, qqd....."#,
); );
assert_eq!(expected.inner, board.inner); assert_eq!(expected.inner, board.inner);
} }
{ {
@ -966,7 +962,13 @@ mod test {
let given = Piece::Drone.moves_at(&(4, 1).into()); let given = Piece::Drone.moves_at(&(4, 1).into());
let expected: HashSet<BoardIndex> = HashSet::from([ let expected: HashSet<BoardIndex> = HashSet::from([
(4, 0).into(), (2, 1).into(), (3, 1).into(), (5, 1).into(), (6, 1).into(), (4, 2).into(), (4, 3).into(), (4, 0).into(),
(2, 1).into(),
(3, 1).into(),
(5, 1).into(),
(6, 1).into(),
(4, 2).into(),
(4, 3).into(),
]); ]);
assert_eq!(expected, given, "Drone moves at"); assert_eq!(expected, given, "Drone moves at");
@ -1024,7 +1026,8 @@ mod test {
{ {
// All normal moves for pawn are valid // All normal moves for pawn are valid
let given = board.valid_moves((3, 1).into()); let given = board.valid_moves((3, 1).into());
let expected: HashSet<BoardIndex> = HashSet::from([(2, 0).into(), (4, 0).into(), (2, 2).into(), (4, 2).into()]); let expected: HashSet<BoardIndex> =
HashSet::from([(2, 0).into(), (4, 0).into(), (2, 2).into(), (4, 2).into()]);
assert_eq!(expected, given); assert_eq!(expected, given);
// Pawn + Pawn on same side = Promotion // Pawn + Pawn on same side = Promotion
@ -1096,7 +1099,13 @@ pub(crate) fn update_board(
mut next_state: ResMut<NextState<TurnState>>, mut next_state: ResMut<NextState<TurnState>>,
) { ) {
// Each move event we get // Each move event we get
events.read().for_each(|Move { from, to, move_type, .. }| { events.read().for_each(
|Move {
from,
to,
move_type,
..
}| {
// Iterate over all pieces // Iterate over all pieces
pieces.iter_mut().for_each(|(entity, mut index)| { pieces.iter_mut().for_each(|(entity, mut index)| {
// If the current index is the 'from' for the move // If the current index is the 'from' for the move
@ -1117,11 +1126,9 @@ pub(crate) fn update_board(
if *from != *to_idx { if *from != *to_idx {
match move_type { match move_type {
MoveType::Promotion(piece) => { MoveType::Promotion(piece) => {
commands commands.entity(entity).insert(*piece);
.entity(entity)
.insert(*piece);
} }
_ => () _ => (),
} }
let ns = !*curr_state.get(); let ns = !*curr_state.get();
@ -1130,8 +1137,7 @@ pub(crate) fn update_board(
} }
} }
// We are moving off the board (e.g,. capture or promotion/merge) // We are moving off the board (e.g,. capture or promotion/merge)
None => { None => match move_type {
match move_type {
MoveType::Capture => { MoveType::Capture => {
debug!("Capturing piece {:?}", entity); debug!("Capturing piece {:?}", entity);
commands commands
@ -1139,18 +1145,17 @@ pub(crate) fn update_board(
.remove::<BoardIndex>() .remove::<BoardIndex>()
.insert(BeingCaptured); .insert(BeingCaptured);
audio_events.send(AudioEvent::Captured); audio_events.send(AudioEvent::Captured);
}, }
MoveType::Promotion(..) => { MoveType::Promotion(..) => {
commands commands
.entity(entity) .entity(entity)
.remove::<BoardIndex>() .remove::<BoardIndex>()
.insert((Promoted, Visibility::Hidden)); .insert((Promoted, Visibility::Hidden));
}, }
_ => { _ => {
panic!("How did you do this!?"); panic!("How did you do this!?");
} }
} },
}
} }
} }
}); });
@ -1158,7 +1163,8 @@ pub(crate) fn update_board(
debug!("De-selecting selected piece {:?}", entity); debug!("De-selecting selected piece {:?}", entity);
commands.entity(entity).remove::<Selected>(); commands.entity(entity).remove::<Selected>();
}); });
}); },
);
*played = false; *played = false;
} }
@ -1331,7 +1337,13 @@ fn clear_endgame(query: Query<Entity, With<Endgame>>, mut commands: Commands) {
/// * All captured pieces have their captured side preserved /// * All captured pieces have their captured side preserved
/// We can iterate over these pieces and calculate the score on the fly /// We can iterate over these pieces and calculate the score on the fly
fn manage_score( fn manage_score(
query: Query<(&Side, &Piece), (Or<(With<Captured>, With<BeingCaptured>)>, With<display3d::Display3d>)>, query: Query<
(&Side, &Piece),
(
Or<(With<Captured>, With<BeingCaptured>)>,
With<display3d::Display3d>,
),
>,
mut debug_info: ResMut<debug::DebugInfo>, mut debug_info: ResMut<debug::DebugInfo>,
mut score: ResMut<Score>, mut score: ResMut<Score>,
) { ) {
@ -1340,11 +1352,20 @@ fn manage_score(
score.captures_b = query.iter().filter(|(s, _)| **s == Side::A).count(); score.captures_b = query.iter().filter(|(s, _)| **s == Side::A).count();
// Calculate score based on the piece values // Calculate score based on the piece values
score.score_a = query.iter().filter(|(s, _)| **s == Side::B).fold(0, |acc, (_, piece)| acc + piece.value()); score.score_a = query
score.score_b = query.iter().filter(|(s, _)| **s == Side::A).fold(0, |acc, (_, piece)| acc + piece.value()); .iter()
.filter(|(s, _)| **s == Side::B)
.fold(0, |acc, (_, piece)| acc + piece.value());
score.score_b = query
.iter()
.filter(|(s, _)| **s == Side::A)
.fold(0, |acc, (_, piece)| acc + piece.value());
// Debug this for good measure // Debug this for good measure
debug_info.set("score".into(), format!("A:{}|B:{}", score.score_a, score.score_b)); debug_info.set(
"score".into(),
format!("A:{}|B:{}", score.score_a, score.score_b),
);
} }
pub(crate) fn set_side( pub(crate) fn set_side(
@ -1517,7 +1538,8 @@ fn reset_game(
.iter() .iter()
.zip(board.pieces().iter()) .zip(board.pieces().iter())
.for_each(|(e, (i, p))| { .for_each(|(e, (i, p))| {
commands.entity(e) commands
.entity(e)
.insert((*i, *p, Visibility::Inherited)) .insert((*i, *p, Visibility::Inherited))
.remove::<(BeingCaptured, Captured, Promoted)>(); .remove::<(BeingCaptured, Captured, Promoted)>();
}); });
@ -1540,7 +1562,10 @@ fn assert_piece_consistency(
let active_count = active.iter().len(); let active_count = active.iter().len();
let being_captured_count = being_captured.iter().len(); let being_captured_count = being_captured.iter().len();
let captured_count = captured.iter().len(); let captured_count = captured.iter().len();
debug!("Active: {} | being captured: {} | captured: {}", active_count, being_captured_count, captured_count); debug!(
"Active: {} | being captured: {} | captured: {}",
active_count, being_captured_count, captured_count
);
let total_count = active_count + being_captured_count + captured_count; let total_count = active_count + being_captured_count + captured_count;
assert_eq!(total_count, 18, "Pieces does does not add up!"); assert_eq!(total_count, 18, "Pieces does does not add up!");
} }
@ -1557,45 +1582,38 @@ fn undo_move(
// Keep track of the current side in case we need to go back // Keep track of the current side in case we need to go back
let mut side = turn.get().0; let mut side = turn.get().0;
board board.undo_move().iter().for_each(
.undo_move() |Move {
.iter() epoch, from, to, ..
.for_each(|Move { epoch, from, to, .. }| { }| {
// If we have any moves to do, go back to the opposite side // If we have any moves to do, go back to the opposite side
if side == turn.get().0 { if side == turn.get().0 {
side = !turn.get().0; side = !turn.get().0;
} }
debug!("Reverting move {:?} {:?} -> {:?}", epoch, from, to); debug!("Reverting move {:?} {:?} -> {:?}", epoch, from, to);
match to { Some(to_idx) => { match to {
Some(to_idx) => {
debug!("Moving piece back from {:?}", to_idx); debug!("Moving piece back from {:?}", to_idx);
// Find piece currently at "to_idx" and update it's position to "from" // Find piece currently at "to_idx" and update it's position to "from"
active_pieces active_pieces
.iter_mut() .iter_mut()
.filter(|idx| { .filter(|idx| (idx.x, idx.y) == (to_idx.x, to_idx.y))
(idx.x, idx.y) == (to_idx.x, to_idx.y) .for_each(|mut idx| *idx = *from);
}) }
.for_each(|mut idx| {
*idx = *from
});
},
None => { None => {
captured_pieces captured_pieces
.iter() .iter()
.find_map(|(entity, captured)| { .find_map(|(entity, captured)| (captured.epoch == *epoch).then_some(entity))
(captured.epoch == *epoch).then_some(entity)
})
.iter() .iter()
.for_each(|entity| { .for_each(|entity| {
commands commands.entity(*entity).remove::<Captured>().insert(*from);
.entity(*entity)
.remove::<Captured>()
.insert(*from);
}); });
} }
} }
}); },
);
// Set the turn state (may be a no-op) // Set the turn state (may be a no-op)
next_turn.set(TurnState(side)); next_turn.set(TurnState(side));

@ -85,7 +85,9 @@ pub(crate) fn intersects_aabb_3d(ray: &Ray3d, aabb: &Aabb, gt: &GlobalTransform)
if t_max.z < hit_far { if t_max.z < hit_far {
hit_far = t_max.z; hit_far = t_max.z;
} }
Some(Hit3d { distance: (hit_near + hit_far) / 2.0 }) Some(Hit3d {
distance: (hit_near + hit_far) / 2.0,
})
} }
/// Heavily synthesized from these two resources: /// Heavily synthesized from these two resources:

@ -31,13 +31,12 @@ impl Plugin for IntroPlugin {
// Play intro manages playing the intro of each individual paragraph // Play intro manages playing the intro of each individual paragraph
// Runs every time the TextScroll component (managed by manage_scroll_text_animation) is updated // Runs every time the TextScroll component (managed by manage_scroll_text_animation) is updated
scroll_text.run_if(any_with_component::<ui::TextScroll>), scroll_text.run_if(any_with_component::<ui::TextScroll>),
set_text_animation_speed set_text_animation_speed.run_if(
.run_if(
just_pressed(KeyCode::Enter) just_pressed(KeyCode::Enter)
.or_else(just_pressed(MouseButton::Left)) .or_else(just_pressed(MouseButton::Left))
.or_else(just_released(KeyCode::Enter)) .or_else(just_released(KeyCode::Enter))
.or_else(just_released(MouseButton::Left)) .or_else(just_released(MouseButton::Left)),
) ),
) )
.run_if(in_state(GameState::Intro)), .run_if(in_state(GameState::Intro)),
); );
@ -63,7 +62,8 @@ fn set_text_animation_speed(
keys: Res<ButtonInput<KeyCode>>, keys: Res<ButtonInput<KeyCode>>,
mouse: Res<ButtonInput<MouseButton>>, mouse: Res<ButtonInput<MouseButton>>,
) { ) {
*animation_speed = if keys.just_pressed(KeyCode::Enter) || mouse.just_pressed(MouseButton::Left) { *animation_speed = if keys.just_pressed(KeyCode::Enter) || mouse.just_pressed(MouseButton::Left)
{
let tweak = tweaks let tweak = tweaks
.get(tweaks_file.handle.clone()) .get(tweaks_file.handle.clone())
.expect("Load tweakfile"); .expect("Load tweakfile");
@ -300,7 +300,9 @@ fn scroll_text(
*vis = Visibility::Inherited; *vis = Visibility::Inherited;
} }
text_scroll.stopwatch.tick(Duration::from_secs_f32(time.delta_seconds() * animation_speed.0)); text_scroll.stopwatch.tick(Duration::from_secs_f32(
time.delta_seconds() * animation_speed.0,
));
text_scroll.progress = { text_scroll.progress = {
// Update animation progress for this paragrpah // Update animation progress for this paragrpah

@ -65,21 +65,23 @@ fn loading(
.ids() .ids()
.filter(|id| matches!(id, AssetId::Uuid { .. })) .filter(|id| matches!(id, AssetId::Uuid { .. }))
.filter(|id| server.get_path(*id).is_some()) .filter(|id| server.get_path(*id).is_some())
.all(|id| { .all(|id| server.is_loaded_with_dependencies(id));
server.is_loaded_with_dependencies(id)
});
let g = (!gltfs.is_empty()) && gltfs.ids().all(|id| server.is_loaded_with_dependencies(id)); let g = (!gltfs.is_empty()) && gltfs.ids().all(|id| server.is_loaded_with_dependencies(id));
let t = (!tweaks.is_empty()) let t = (!tweaks.is_empty())
&& tweaks && tweaks
.ids() .ids()
.all(|id| server.is_loaded_with_dependencies(id)); .all(|id| server.is_loaded_with_dependencies(id));
let f = !fonts.is_empty() && let f = !fonts.is_empty()
fonts.ids() && fonts
.ids()
.filter(|id| server.get_path(*id).is_some()) .filter(|id| server.get_path(*id).is_some())
.all(|id| server.is_loaded_with_dependencies(id)); .all(|id| server.is_loaded_with_dependencies(id));
if s && g && t && f { if s && g && t && f {
debug!("Loading complete after {:?} seconds", time.elapsed_seconds()); debug!(
"Loading complete after {:?} seconds",
time.elapsed_seconds()
);
debug!("Starting game intro"); debug!("Starting game intro");
next_state.set(GameState::Intro) next_state.set(GameState::Intro)
} }

@ -22,7 +22,7 @@ use crate::prelude::*;
fn main() { fn main() {
if std::env::var_os("CARGO_MANIFEST_DIR").is_none() { if std::env::var_os("CARGO_MANIFEST_DIR").is_none() {
std::env::set_var("CARGO_MANIFEST_DIR", "."); std::env::set_current_dir(std::env::current_exe().unwrap().parent().unwrap());
} }
let mut app = App::new(); let mut app = App::new();
@ -37,7 +37,10 @@ fn main() {
.run_if(resource_changed::<State<tutorial::TutorialState>>), .run_if(resource_changed::<State<tutorial::TutorialState>>),
), ),
); );
#[cfg(not(target_os = "macos"))]
app.add_systems(Startup, set_window_icon); app.add_systems(Startup, set_window_icon);
app.add_plugins( app.add_plugins(
DefaultPlugins DefaultPlugins
.set(ImagePlugin::default_nearest()) .set(ImagePlugin::default_nearest())
@ -48,7 +51,8 @@ fn main() {
}), }),
..default() ..default()
}), }),
).add_systems(Update, handle_window.run_if(just_pressed(KeyCode::F11))); )
.add_systems(Update, handle_window.run_if(just_pressed(KeyCode::F11)));
app.add_plugins(credits::CreditsPlugin); app.add_plugins(credits::CreditsPlugin);
app.add_plugins(debug::DebugPlugin); app.add_plugins(debug::DebugPlugin);
@ -56,12 +60,13 @@ fn main() {
app.add_plugins(game::GamePlugin); app.add_plugins(game::GamePlugin);
app.add_plugins(loading::LoadingPlugin); app.add_plugins(loading::LoadingPlugin);
app.add_plugins(menu::MenuPlugin); app.add_plugins(menu::MenuPlugin);
app.add_plugins(audio::AudioPlugin);
app.add_plugins(ui::UiPlugin); app.add_plugins(ui::UiPlugin);
app.add_plugins(tweak::TweakPlugin); app.add_plugins(tweak::TweakPlugin);
app.add_plugins(intro::IntroPlugin); app.add_plugins(intro::IntroPlugin);
app.add_plugins(tutorial::TutorialPlugin); app.add_plugins(tutorial::TutorialPlugin);
app.add_plugins(ai::AiPlugin); app.add_plugins(ai::AiPlugin);
app.add_plugins(audio::AudioPlugin);
app.run(); app.run();
} }
@ -143,6 +148,7 @@ pub(crate) fn _any_component_added_or_changed<C: Component>(
!q.is_empty() !q.is_empty()
} }
#[cfg(not(target_os = "macos"))]
fn set_window_icon( fn set_window_icon(
// we have to use `NonSend` here // we have to use `NonSend` here
windows: NonSend<WinitWindows>, windows: NonSend<WinitWindows>,
@ -186,10 +192,7 @@ where
Box::new(move |buttons: Res<ButtonInput<T>>| -> bool { buttons.pressed(button) }) Box::new(move |buttons: Res<ButtonInput<T>>| -> bool { buttons.pressed(button) })
} }
fn handle_window(mut query: Query<&mut Window>) {
fn handle_window(
mut query: Query<&mut Window>
) {
use bevy::window::WindowMode; use bevy::window::WindowMode;
query.iter_mut().for_each(|mut window| { query.iter_mut().for_each(|mut window| {

@ -26,7 +26,10 @@ impl Plugin for MenuPlugin {
) )
.run_if(any_component_changed::<Interaction>()), .run_if(any_component_changed::<Interaction>()),
) )
.add_systems(Update, manage_ai_button.run_if(state_changed::<ai::PlayState>)) .add_systems(
Update,
manage_ai_button.run_if(state_changed::<ai::PlayState>),
)
.add_systems(Update, handle_escape.run_if(just_pressed(KeyCode::Escape))); .add_systems(Update, handle_escape.run_if(just_pressed(KeyCode::Escape)));
} }
} }
@ -386,7 +389,6 @@ fn handle_button_press<S: States + Clone + Component>(
}); });
} }
fn manage_ai_button( fn manage_ai_button(
curr: Res<State<ai::PlayState>>, curr: Res<State<ai::PlayState>>,
mut query: Query<(&mut ButtonAction<PlayState>, &Children)>, mut query: Query<(&mut ButtonAction<PlayState>, &Children)>,
@ -403,7 +405,7 @@ fn manage_ai_button(
t.sections.iter_mut().for_each(|s| { t.sections.iter_mut().for_each(|s| {
s.value = match ba.0 { s.value = match ba.0 {
PlayState::AiBogo => "AI Opponent".into(), PlayState::AiBogo => "AI Opponent".into(),
PlayState::Human => "Human Opponent".into() PlayState::Human => "Human Opponent".into(),
} }
}); });
}); });

@ -1,4 +1,4 @@
pub(crate) use crate::{audio::AudioEvent, game::*, menu::*, tutorial::*, tweak::*, ai::*, *}; pub(crate) use crate::{ai::*, audio::AudioEvent, game::*, menu::*, tutorial::*, tweak::*, *};
pub(crate) use bevy::{ pub(crate) use bevy::{
animation::RepeatAnimation, animation::RepeatAnimation,

@ -52,8 +52,9 @@ impl Plugin for TutorialPlugin {
.run_if(in_state(MenuState::Off)) .run_if(in_state(MenuState::Off))
.run_if(not(resource_exists::<TutorialStarted>)) .run_if(not(resource_exists::<TutorialStarted>))
.run_if(any_component_removed::<display3d::Dissolving>()), .run_if(any_component_removed::<display3d::Dissolving>()),
clear_tutorial_progress.run_if(on_event::<StateTransitionEvent<TutorialState>>()), clear_tutorial_progress
) .run_if(on_event::<StateTransitionEvent<TutorialState>>()),
),
); );
} }
} }
@ -514,7 +515,8 @@ fn step(
// Default, empty (tutorial doesn't always need to show something) // Default, empty (tutorial doesn't always need to show something)
else { else {
// When prompting to show a piece, find a random piece on this side and high-light it // When prompting to show a piece, find a random piece on this side and high-light it
all_pieces.iter() all_pieces
.iter()
.find_map(|(piece, board_index, side)| { .find_map(|(piece, board_index, side)| {
if *side == side_state.get().0 { if *side == side_state.get().0 {
if !queen_seen && *piece == Piece::Queen { if !queen_seen && *piece == Piece::Queen {
@ -576,7 +578,8 @@ fn start_tutorial_on_play(
mut next_state: ResMut<NextState<TutorialState>>, mut next_state: ResMut<NextState<TutorialState>>,
mut commands: Commands, mut commands: Commands,
) { ) {
if query.iter().len() == 0 && *state.get() == TutorialState::None && board.current_epoch() <= 1 { if query.iter().len() == 0 && *state.get() == TutorialState::None && board.current_epoch() <= 1
{
debug!("Starting intro tutorial!"); debug!("Starting intro tutorial!");
next_state.set(TutorialState::Intro); next_state.set(TutorialState::Intro);
commands.insert_resource(TutorialStarted); commands.insert_resource(TutorialStarted);
@ -587,5 +590,8 @@ fn clear_tutorial_progress(
mut events: EventReader<StateTransitionEvent<TutorialState>>, mut events: EventReader<StateTransitionEvent<TutorialState>>,
mut seen: ResMut<SeenStates>, mut seen: ResMut<SeenStates>,
) { ) {
events.read().filter(|StateTransitionEvent { after, .. }| *after == TutorialState::None).for_each(|_| seen.0.clear()); events
.read()
.filter(|StateTransitionEvent { after, .. }| *after == TutorialState::None)
.for_each(|_| seen.0.clear());
} }
Loading…
Cancel
Save