Skip to content

Commit

Permalink
wip: Extremely basic mob behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
mizkyosia committed Feb 3, 2025
1 parent 030f324 commit d907395
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 56 deletions.
1 change: 0 additions & 1 deletion client/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ pub fn game_plugin(app: &mut App) {
(
setup_fox_once_loaded,
simulate_particles,
add_mob_markers,
update_targetted_mob_color,
stack_update_system,
)
Expand Down
18 changes: 9 additions & 9 deletions client/src/mob/fox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,15 @@ pub fn setup_fox_once_loaded(
}
}

pub fn add_mob_markers(mut commands: Commands, query: Query<(&MobMarker, &Children)>) {
// NOTE: This is arguably a ridiculous solution, this iterates on all mobs every frame to recursively add the Mob component to all children of a mob.
// Optimize later to only run once when the Mob is spawned.
for (mob, children) in query.iter() {
for child in children.iter() {
commands.entity(*child).insert_if_new(mob.clone());
}
}
}
// pub fn add_mob_markers(mut commands: Commands, query: Query<(&MobMarker, &Children)>) {
// // NOTE: This is arguably a ridiculous solution, this iterates on all mobs every frame to recursively add the Mob component to all children of a mob.
// // Optimize later to only run once when the Mob is spawned.
// for (mob, children) in query.iter() {
// for child in children.iter() {
// commands.entity(*child).insert_if_new(mob.clone());
// }
// }
// }

pub fn simulate_particles(
mut commands: Commands,
Expand Down
24 changes: 19 additions & 5 deletions client/src/mob/spawn.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bevy::prelude::*;
use shared::messages::mob::MobUpdateEvent;

use crate::mob::setup_fox;
use crate::{mob::setup_fox, player::CurrentPlayerMarker, world::RenderDistance};

use super::MobRoot;

Expand All @@ -10,23 +10,37 @@ pub fn spawn_mobs_system(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut graphs: ResMut<Assets<AnimationGraph>>,
mut mobs: Query<(&MobRoot, &mut Transform)>,
mut mobs: Query<(Entity, &MobRoot, &mut Transform), Without<CurrentPlayerMarker>>,
player_pos: Query<&Transform, With<CurrentPlayerMarker>>,
render_distance: Res<RenderDistance>,
) {
let player_pos = player_pos.single().translation;

'event_loop: for event in ev_update.read() {
let id = event.mob.id;
let id = event.id;

let position = event.mob.position;

for (mob, mut transform) in mobs.iter_mut() {
for (_, mob, mut transform) in mobs.iter_mut() {
if mob.id == id {
transform.translation = position;
transform.rotation = event.mob.rotation;
continue 'event_loop;
}
}

if event.mob.kind == shared::world::MobKind::Fox {
if event.mob.kind == shared::world::MobKind::Fox
&& event.mob.position.distance(player_pos) < render_distance.distance as f32 * 5.0
{
info!("Spawning fox at {:?}", position);
setup_fox(id, position, &mut commands, &asset_server, &mut graphs);
}
}

// Despawn entities which are too far away
for (entity, _, transform) in mobs.iter() {
if transform.translation.distance(player_pos) > render_distance.distance as f32 * 5.0 {
commands.entity(entity).despawn_recursive();
}
}
}
6 changes: 3 additions & 3 deletions client/src/network/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ pub fn update_world_from_network(
ev_render.send(WorldRenderRequestUpdateEvent::ChunkToReload(pos));
}

for mob in world_update.mobs {
for (id, mob) in world_update.mobs {
debug!("ServerMob received: {:?}", mob);
ev_mob_update.send(MobUpdateEvent { mob });
ev_mob_update.send(MobUpdateEvent { id, mob });
}

ev_item_stacks_update.send_batch(world_update.item_stacks);
Expand All @@ -65,7 +65,7 @@ pub fn update_world_from_network(
}
ServerToClientMessage::MobUpdate(update_event) => {
info!("Received mob update event {:?}", update_event);
// this is not currently used
ev_mob_update.send(update_event);
}
ServerToClientMessage::PlayerUpdate(update) => {
ev_player_update.send(update);
Expand Down
43 changes: 43 additions & 0 deletions server/src/mob/behavior.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use bevy::{
math::{ops::atan2, Quat},
time::{Fixed, Time},
};
use bevy_ecs::system::{Res, ResMut};
use shared::world::{MobAction, MobTarget, ServerWorldMap};

pub fn mob_behavior_system(mut world_map: ResMut<ServerWorldMap>, delta: Res<Time<Fixed>>) {
let mut mobs = world_map.mobs.clone();

for (_mob_id, mob) in mobs.iter_mut() {
let target = match mob.target {
MobTarget::Position(pos) => pos,
MobTarget::None => continue,
MobTarget::Player(id) => world_map.players.get(&id).unwrap().position,
MobTarget::Mob(id) => world_map.mobs.get(&id).unwrap().position,
};

let dir = (target - mob.position).normalize();
let velocity = 2.0 * delta.delta_secs();

match mob.action {
MobAction::Walk | MobAction::Attack => {
mob.position += dir * velocity;
mob.rotation = Quat::from_rotation_y(atan2(dir.x, dir.z));

// If reached destination, start idling
if mob.position.distance(target) < 0.5 {
mob.action = MobAction::Flee;
}
}
MobAction::Flee => {
if mob.position.distance(target) < 15.0 {
mob.position -= dir * velocity;
mob.rotation = Quat::from_rotation_y(atan2(-dir.x, -dir.z));
}
}
_ => {}
}
}

world_map.mobs = mobs;
}
14 changes: 9 additions & 5 deletions server/src/mob/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod behavior;

use bevy::prelude::*;
use shared::world::{ServerMob, ServerWorldMap};
use shared::world::{MobAction, MobKind, MobTarget, ServerMob, ServerWorldMap};
use ulid::Ulid;

use crate::init::ServerTime;
Expand All @@ -9,21 +11,23 @@ fn create_new_mob_id() -> u128 {
}

pub fn manage_mob_spawning_system(mut world_map: ResMut<ServerWorldMap>, time: Res<ServerTime>) {
if time.0 == 300 {
if time.0 == 100 {
debug!("Should spawn mob");

let id = create_new_mob_id();

let position = Vec3::new(0.0, 90.0, 0.0);

let mob = ServerMob {
id,
kind: shared::world::MobKind::Fox,
kind: MobKind::Fox,
position,
target: MobTarget::Player(*world_map.players.keys().next().unwrap()),
action: MobAction::Walk,
rotation: Quat::IDENTITY,
};

info!("Spawning new mob on server: {:?}", mob);

world_map.mobs.push(mob);
world_map.mobs.insert(id, mob);
}
}
3 changes: 3 additions & 0 deletions server/src/network/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::init::{LobbyPlayer, ServerLobby, ServerTime};
use crate::mob::behavior::mob_behavior_system;
use crate::network::broadcast_chat::*;
use crate::network::cleanup::cleanup_player_from_world;
use crate::world;
Expand Down Expand Up @@ -42,6 +43,8 @@ pub fn register_systems(app: &mut App) {
app.add_systems(Update, background_world_generation_system);

app.add_systems(PostUpdate, update_server_time);

app.add_systems(FixedUpdate, mob_behavior_system);
}

fn server_update_system(
Expand Down
59 changes: 34 additions & 25 deletions server/src/world/broadcast_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ use bevy::math::IVec3;
use bevy::prelude::*;
use bevy_ecs::system::ResMut;
use bevy_renet::renet::RenetServer;
use shared::messages::mob::MobUpdateEvent;
use shared::messages::{ItemStackUpdateEvent, PlayerId, ServerToClientMessage, WorldUpdate};
use shared::players::Player;
use shared::world::{
world_position_to_chunk_position, ServerChunk, ServerChunkWorldMap, ServerWorldMap,
};
use shared::CHUNK_SIZE;
use std::collections::HashMap;

pub const BROADCAST_RENDER_DISTANCE: i32 = 1;
Expand All @@ -29,36 +31,43 @@ pub fn broadcast_world_state(
let players = &mut world_map.players;
let chunks = &mut world_map.chunks;

for c in get_all_active_chunks(players, BROADCAST_RENDER_DISTANCE) {
let chunk = chunks.map.get(&c);
for client in server.clients_id().iter_mut() {
let player = players.get_mut(client);
let player = match player {
Some(p) => p.clone(),
None => continue,
};

for (id, mob) in mobs.iter() {
if mob.position.distance(player.position)
< (BROADCAST_RENDER_DISTANCE * CHUNK_SIZE) as f32
{
server.send_game_message(
*client,
ServerToClientMessage::MobUpdate(MobUpdateEvent {
id: *id,
mob: mob.clone(),
}),
);
}
}

if chunk.is_none() {
let msg = WorldUpdate {
tick: time.0,
time: ts,
new_map: get_world_map_chunks_to_send(chunks, players, &player),
mobs: mobs.clone(),
item_stacks: get_items_stacks(),
player_events: vec![],
};

if msg.new_map.is_empty() {
continue;
}

for client in server.clients_id().iter_mut() {
let player = players.get_mut(client);
let player = match player {
Some(p) => p.clone(),
None => continue,
};
let msg = WorldUpdate {
tick: time.0,
time: ts,
new_map: get_world_map_chunks_to_send(chunks, players, &player),
mobs: mobs.clone(),
item_stacks: get_items_stacks(),
player_events: vec![],
};

if msg.new_map.is_empty() {
continue;
}

let message = ServerToClientMessage::WorldUpdate(msg);
let message = ServerToClientMessage::WorldUpdate(msg);

server.send_game_message(*client, message);
}
server.send_game_message(*client, message);
}
}

Expand Down
3 changes: 2 additions & 1 deletion shared/src/messages/mob.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use bevy::prelude::*;
use serde::{Deserialize, Serialize};

use crate::world::ServerMob;
use crate::world::{MobId, ServerMob};

#[derive(Event, Serialize, Deserialize, Debug, Clone)]
pub struct MobUpdateEvent {
pub id: MobId,
pub mob: ServerMob,
}
4 changes: 2 additions & 2 deletions shared/src/messages/world.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashMap;

use crate::world::{ItemStack, ServerChunk, ServerMob};
use crate::world::{ItemStack, MobId, ServerChunk, ServerMob};
use bevy::{
math::{IVec3, Vec3},
prelude::Event,
Expand All @@ -16,7 +16,7 @@ pub struct WorldUpdate {
pub tick: u64,
pub time: u64,
pub new_map: HashMap<IVec3, ServerChunk>,
pub mobs: Vec<ServerMob>,
pub mobs: HashMap<MobId, ServerMob>,
pub item_stacks: Vec<ItemStackUpdateEvent>,
pub player_events: Vec<PlayerUpdateEvent>,
}
Expand Down
4 changes: 2 additions & 2 deletions shared/src/players/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ pub fn simulate_player_movement(
direction += right;
}
if action.is_pressed(NetworkAction::JumpOrFlyUp) {
direction += Vec3::new(0.0, 1.0, 0.0);
direction += Vec3::Y;
}
if action.is_pressed(NetworkAction::SneakOrFlyDown) {
direction -= Vec3::new(0.0, 1.0, 0.0);
direction -= Vec3::Y;
}

// Handle jumping (if on the ground) and gravity, only if not flying
Expand Down
3 changes: 2 additions & 1 deletion shared/src/world/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::fmt::Debug;
use super::BlockData;
use super::ItemId;
use super::ItemType;
use super::MobId;
use super::ServerMob;

#[derive(Debug, Default, Serialize, Deserialize, Clone)]
Expand Down Expand Up @@ -41,7 +42,7 @@ pub struct ServerWorldMap {
pub name: String,
pub chunks: ServerChunkWorldMap,
pub players: HashMap<PlayerId, Player>,
pub mobs: Vec<ServerMob>,
pub mobs: HashMap<MobId, ServerMob>,
pub item_stacks: Vec<ServerItemStack>,
pub time: u64,
}
Expand Down
26 changes: 24 additions & 2 deletions shared/src/world/mobs.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
use bevy::math::Vec3;
use bevy::math::{Quat, Vec3};
use serde::{Deserialize, Serialize};

use crate::messages::PlayerId;

pub type MobId = u128;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum MobKind {
Fox,
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum MobTarget {
None,
Position(Vec3),
Player(PlayerId),
Mob(MobId),
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum MobAction {
Idle,
Walk,
Attack,
Flee,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ServerMob {
pub id: u128,
pub kind: MobKind,
pub target: MobTarget,
pub action: MobAction,
pub position: Vec3,
pub rotation: Quat,
}

0 comments on commit d907395

Please sign in to comment.