/*
* This file is part of laurelin_client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
#![allow(clippy::too_many_arguments)]
#![allow(clippy::derivable_impls)]
#![allow(clippy::type_complexity)]
use api::user::User;
use bevy::{
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
};
use bevy_assets_bundler::*;
use bevy_editor_pls::EditorPlugin;
use bevy_egui::EguiPlugin;
use bevy_mod_picking::prelude::*;
use bevy_rapier3d::prelude::*;
use bevy_text_mesh::prelude::*;
mod api;
mod macros;
mod util;
mod plugins;
mod game_status;
#[macro_export]
macro_rules! seed_gen {
() => {
fastrand::u64(u64::MIN..=u64::MAX)
};
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, States)]
enum AppState {
#[default]
Menu,
InGame,
}
fn main() {
let key = [
25, 221, 123, 82, 182, 95, 251, 128, 83, 192, 172, 139, 92, 102, 34, 248,
];
let mut asset_bundling_options = AssetBundlingOptions::default();
asset_bundling_options.set_encryption_key(key);
asset_bundling_options.encode_file_names = true;
asset_bundling_options.enabled_on_debug_build = true;
let mut app = App::new();
app.add_plugins(
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
title: String::from("Laurelin Client"),
..Default::default()
}),
..Default::default()
})
.build()
.add_before::<AssetPlugin, _>(BundledAssetIoPlugin::from(asset_bundling_options)),
);
app.add_plugin(RapierPhysicsPlugin::<NoUserData>::default());
app.add_plugin(TextMeshPlugin);
app.add_plugins(DefaultPickingPlugins);
app.insert_resource(AmbientLight {
color: Color::ANTIQUE_WHITE,
brightness: 0.4,
});
info!("laurelin client initializing");
// dev
app.add_plugin(RapierDebugRenderPlugin::default());
app.add_plugin(EditorPlugin::new());
app.insert_resource(editor_controls());
// the egui plugin is also added by the editor plugin
if !app.is_plugin_added::<EguiPlugin>() {
app.add_plugin(EguiPlugin);
}
// clear color
app.insert_resource(ClearColor(Color::rgb(0.53, 0.53, 0.7)));
// add the top level app state
app.add_state::<AppState>();
// holds networking options and a request agent
app.register_type::<NetworkingOptions>()
.init_resource::<NetworkingOptions>();
app.register_type::<Global>()
.register_type::<User>()
.init_resource::<Global>();
// has handlers for all async tasks
app.add_plugin(plugins::AsyncTasksPlugin);
app.add_plugin(plugins::MenuPlugin);
app.add_plugin(plugins::GamePlugin);
app.add_startup_system(setup).add_system(move_camera);
app.run();
info!("goodbye");
}
#[derive(Component)]
struct PlayerCamera;
fn setup(mut commands: Commands) {
commands
.spawn((
Camera3dBundle {
transform: Transform {
translation: Vec3::new(0., 5.5, 5.8),
rotation: Quat::from_rotation_x(-1.2),
..Default::default()
},
..Default::default()
},
RapierPickCamera::default(),
))
.insert(PlayerCamera);
}
fn move_camera(
time: Res<Time>,
input: Res<Input<KeyCode>>,
mut mouse_wheel_ev_r: EventReader<MouseWheel>,
mut camera_query: Query<(&PlayerCamera, &mut Transform)>,
) {
for (_camera, mut transform) in &mut camera_query {
let mut direction = Vec3::ZERO;
let y_modifier = 3.;
for ev in mouse_wheel_ev_r.iter() {
match ev.unit {
MouseScrollUnit::Line => {
direction.y -= 20.0 * ev.y.signum() * y_modifier * time.delta_seconds()
}
MouseScrollUnit::Pixel => direction.y -= ev.y * y_modifier * time.delta_seconds(),
}
}
if input.pressed(KeyCode::A) {
direction.x -= 1.;
}
if input.pressed(KeyCode::D) {
direction.x += 1.;
}
if input.pressed(KeyCode::W) {
direction.z -= 1.;
}
if input.pressed(KeyCode::S) {
direction.z += 1.;
}
let cam_speed = 4.;
transform.translation += direction * cam_speed * time.delta_seconds();
}
}
fn editor_controls() -> bevy_editor_pls::controls::EditorControls {
use bevy_editor_pls::controls;
let mut editor_controls = controls::EditorControls::default_bindings();
editor_controls.unbind(controls::Action::PlayPauseEditor);
editor_controls.insert(
controls::Action::PlayPauseEditor,
controls::Binding {
input: controls::UserInput::Single(controls::Button::Keyboard(KeyCode::Escape)),
conditions: vec![controls::BindingCondition::ListeningForText(false)],
},
);
editor_controls
}
#[derive(Clone, Resource, Reflect)]
#[reflect(Resource)]
pub struct NetworkingOptions {
/// api address with no trailing slash
api_address: String,
/// reqwest agent
/// NOTE: mainly for the future, because it can hold cookies
#[reflect(ignore)]
req: reqwest::blocking::Client,
/// feels wrong storing this here but "it's temporary"
user_token: String,
}
impl Default for NetworkingOptions {
fn default() -> Self {
Self {
api_address: "http://localhost:3000/api".to_string(),
req: reqwest::blocking::Client::new(),
user_token: "".to_string(),
}
}
}
#[derive(Resource, Reflect)]
#[reflect(Resource)]
pub struct Global {
/// details of the logged in user
user: Option<User>,
/// current game id, set from browse
cur_game_id: String,
}
impl Default for Global {
fn default() -> Self {
Self {
user: None,
cur_game_id: "".to_string(),
}
}
}