Added initial game logic service
This commit is contained in:
@@ -5,6 +5,7 @@ members = [
|
||||
"chat-service",
|
||||
"character-service",
|
||||
"database-service",
|
||||
"game-logic-service",
|
||||
"packet-service",
|
||||
"world-service",
|
||||
"utils",
|
||||
|
||||
23
game-logic-service/Cargo.toml
Normal file
23
game-logic-service/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "game-logic-service"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
utils = { path = "../utils" }
|
||||
dotenv = "0.15"
|
||||
tokio = { version = "1.45.1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
hecs = { version = "0.10.5", features = ["serde", "default", "std"] }
|
||||
serde_json = "1.0.140"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "chrono"] }
|
||||
rand = "0.8.5"
|
||||
tonic = "0.12.3"
|
||||
prost = "0.13.4"
|
||||
futures = "0.3.31"
|
||||
tokio-stream = "0.1.17"
|
||||
warp = "0.3.7"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.12.3"
|
||||
25
game-logic-service/Dockerfile
Normal file
25
game-logic-service/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM rust:alpine AS builder
|
||||
LABEL authors="raven"
|
||||
|
||||
RUN apk add --no-cache musl-dev libressl-dev protobuf-dev
|
||||
|
||||
WORKDIR /usr/src/utils
|
||||
COPY ./utils .
|
||||
|
||||
WORKDIR /usr/src/proto
|
||||
COPY ./proto .
|
||||
|
||||
WORKDIR /usr/src/game-logic-service
|
||||
COPY ./game-logic-service .
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
FROM alpine:3
|
||||
|
||||
RUN apk add --no-cache libssl3 libgcc
|
||||
|
||||
COPY --from=builder /usr/src/game-logic-service/target/release/game-logic-service /usr/local/bin/game-logic-service
|
||||
|
||||
EXPOSE 50056
|
||||
|
||||
CMD ["game-logic-service"]
|
||||
17
game-logic-service/build.rs
Normal file
17
game-logic-service/build.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
fn main() {
|
||||
// gRPC Server code
|
||||
tonic_build::configure()
|
||||
.build_server(true) // Generate gRPC server code
|
||||
.compile_well_known_types(true)
|
||||
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
|
||||
.compile_protos(&["../proto/game_logic.proto", "../proto/game.proto"], &["../proto"])
|
||||
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
||||
|
||||
// gRPC Client code
|
||||
tonic_build::configure()
|
||||
.build_server(false) // Generate gRPC client code
|
||||
.compile_well_known_types(true)
|
||||
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
|
||||
.compile_protos(&["../proto/user_db_api.proto", "../proto/auth.proto", "../proto/character.proto", "../proto/character_common.proto", "../proto/chat.proto", "../proto/world.proto"], &["../proto"])
|
||||
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
||||
}
|
||||
20
game-logic-service/src/components/basic_info.rs
Normal file
20
game-logic-service/src/components/basic_info.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BasicInfo {
|
||||
pub name: String,
|
||||
pub id: u16,
|
||||
pub tag: u32,
|
||||
pub team_id: u32,
|
||||
pub job: u16,
|
||||
pub stat_points: u32,
|
||||
pub skill_points: u32,
|
||||
pub pk_flag: u16,
|
||||
pub stone: u8,
|
||||
pub char_id: u32,
|
||||
}
|
||||
|
||||
impl Default for BasicInfo {
|
||||
fn default() -> Self {
|
||||
Self { name: "TEST".to_string(), id: 0, tag: 0, team_id: 0, job: 0, stat_points: 0, skill_points: 0, pk_flag: 0, stone: 0, char_id: 0 }
|
||||
}
|
||||
}
|
||||
31
game-logic-service/src/components/character_graphics.rs
Normal file
31
game-logic-service/src/components/character_graphics.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CharacterGraphics {
|
||||
race: u8,
|
||||
hair: u8,
|
||||
face: u8,
|
||||
}
|
||||
|
||||
impl Default for CharacterGraphics {
|
||||
fn default() -> Self {
|
||||
Self { race: 0, hair: 0, face: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl CharacterGraphics {
|
||||
pub fn new(race: u8, hair: u8, face: u8) -> Self {
|
||||
Self { race, hair, face }
|
||||
}
|
||||
|
||||
pub fn get_race(&self) -> u8 {
|
||||
self.race
|
||||
}
|
||||
|
||||
pub fn get_hair(&self) -> u8 {
|
||||
self.hair
|
||||
}
|
||||
|
||||
pub fn get_face(&self) -> u8 {
|
||||
self.face
|
||||
}
|
||||
}
|
||||
22
game-logic-service/src/components/client.rs
Normal file
22
game-logic-service/src/components/client.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Client {
|
||||
client_id: u16,
|
||||
access_level: u16,
|
||||
}
|
||||
|
||||
impl Default for Client {
|
||||
fn default() -> Self {
|
||||
Self { client_id: 0, access_level: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn get_client_id(&self) -> u16 {
|
||||
self.client_id
|
||||
}
|
||||
|
||||
pub fn get_access_level(&self) -> u16 {
|
||||
self.access_level
|
||||
}
|
||||
}
|
||||
13
game-logic-service/src/components/destination.rs
Normal file
13
game-logic-service/src/components/destination.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#[derive(Debug)]
|
||||
struct Destination {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
dest: u16,
|
||||
}
|
||||
|
||||
impl Default for Destination {
|
||||
fn default() -> Self {
|
||||
Self { x: 520000.0, y: 520000.0, z: 1.0, dest: 20 }
|
||||
}
|
||||
}
|
||||
30
game-logic-service/src/components/item.rs
Normal file
30
game-logic-service/src/components/item.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
#[derive(Debug)]
|
||||
pub struct Item {
|
||||
pub is_created: bool,
|
||||
pub is_zuly: bool,
|
||||
pub life: f32,
|
||||
pub durability: u8,
|
||||
pub has_socket: bool,
|
||||
pub is_appraised: bool,
|
||||
pub grade: u8,
|
||||
pub count: u32,
|
||||
pub gem_option: u16,
|
||||
pub price: u32,
|
||||
}
|
||||
|
||||
impl Default for Item {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_created: false,
|
||||
is_zuly: false,
|
||||
life: 0.0,
|
||||
durability: 0,
|
||||
has_socket: false,
|
||||
is_appraised: false,
|
||||
grade: 0,
|
||||
count: 0,
|
||||
gem_option: 0,
|
||||
price: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
34
game-logic-service/src/components/level.rs
Normal file
34
game-logic-service/src/components/level.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Level {
|
||||
pub level: u16,
|
||||
pub xp: u64,
|
||||
pub penalty_xp: u64,
|
||||
}
|
||||
|
||||
impl Default for Level {
|
||||
fn default() -> Self {
|
||||
Self { level: 1, xp: 0, penalty_xp: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Level {
|
||||
pub fn get_level(&self) -> u16 {
|
||||
self.level
|
||||
}
|
||||
|
||||
pub fn get_xp(&self) -> u64 {
|
||||
self.xp
|
||||
}
|
||||
|
||||
pub fn get_penalty_xp(&self) -> u64 {
|
||||
self.penalty_xp
|
||||
}
|
||||
|
||||
pub fn add_xp(&mut self, amount: u64) {
|
||||
self.xp += amount;
|
||||
}
|
||||
|
||||
pub fn add_penalty_xp(&mut self, amount: u64) {
|
||||
self.penalty_xp += amount;
|
||||
}
|
||||
}
|
||||
40
game-logic-service/src/components/life.rs
Normal file
40
game-logic-service/src/components/life.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Life {
|
||||
hp: u32,
|
||||
max_hp: u32,
|
||||
}
|
||||
|
||||
impl Default for Life {
|
||||
fn default() -> Self {
|
||||
Self { hp: 100, max_hp: 100 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Life {
|
||||
pub fn new(hp: u32, max_hp: u32) -> Self {
|
||||
Self { hp, max_hp }
|
||||
}
|
||||
|
||||
pub fn get_hp(&self) -> u32 {
|
||||
self.hp
|
||||
}
|
||||
|
||||
pub fn get_max_hp(&self) -> u32 {
|
||||
self.max_hp
|
||||
}
|
||||
|
||||
pub fn take_damage(&mut self, damage: u32) {
|
||||
self.hp = self.hp.saturating_sub(damage);
|
||||
if self.hp <= 0 {
|
||||
self.hp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn heal(&mut self, amount: u32) {
|
||||
self.hp = self.hp.saturating_add(amount);
|
||||
if self.hp > self.max_hp {
|
||||
self.hp = self.max_hp;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
game-logic-service/src/components/magic.rs
Normal file
36
game-logic-service/src/components/magic.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Magic {
|
||||
mp: u32,
|
||||
max_mp: u32,
|
||||
}
|
||||
|
||||
impl Default for Magic {
|
||||
fn default() -> Self {
|
||||
Self { mp: 55, max_mp: 55 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Magic {
|
||||
pub fn get_mp(&self) -> u32 {
|
||||
self.mp
|
||||
}
|
||||
|
||||
pub fn get_max_mp(&self) -> u32 {
|
||||
self.max_mp
|
||||
}
|
||||
|
||||
pub fn use_mp(&mut self, amount: u32) {
|
||||
self.mp = self.mp.saturating_sub(amount);
|
||||
if self.mp <= 0 {
|
||||
self.mp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore_mp(&mut self, amount: u32) {
|
||||
self.mp = self.mp.saturating_add(amount);
|
||||
if self.mp > self.max_mp {
|
||||
self.mp = self.max_mp;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
game-logic-service/src/components/markers.rs
Normal file
28
game-logic-service/src/components/markers.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use hecs::Entity;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mob {
|
||||
pub id: u32,
|
||||
pub quest_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Npc {
|
||||
pub id: u32,
|
||||
pub quest_id: u32,
|
||||
pub angle: f32,
|
||||
pub event_status: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pet {
|
||||
pub owner: Entity,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlayerSpawn {
|
||||
pub point_type: u32
|
||||
}
|
||||
13
game-logic-service/src/components/mod.rs
Normal file
13
game-logic-service/src/components/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub mod markers;
|
||||
pub mod position;
|
||||
pub mod basic_info;
|
||||
pub mod destination;
|
||||
pub mod target;
|
||||
pub mod level;
|
||||
pub mod life;
|
||||
pub mod magic;
|
||||
pub mod client;
|
||||
pub mod item;
|
||||
pub mod stats;
|
||||
pub mod character_graphics;
|
||||
pub mod spawner;
|
||||
20
game-logic-service/src/components/position.rs
Normal file
20
game-logic-service/src/components/position.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Position {
|
||||
#[serde(rename = "X")]
|
||||
pub x: f32,
|
||||
#[serde(rename = "Y")]
|
||||
pub y: f32,
|
||||
#[serde(rename = "Z")]
|
||||
pub z: f32,
|
||||
pub map_id: u16,
|
||||
pub spawn_id: u16,
|
||||
}
|
||||
|
||||
impl Default for Position {
|
||||
fn default() -> Self {
|
||||
// Set the default position to (520000.0, 520000.0)
|
||||
Self { x: 520000.0, y: 520000.0, z: 1.0, map_id: 20, spawn_id: 1 }
|
||||
}
|
||||
}
|
||||
60
game-logic-service/src/components/spawner.rs
Normal file
60
game-logic-service/src/components/spawner.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::time::{Duration, Instant};
|
||||
use hecs::Entity;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Spawner {
|
||||
/// The ID of the mob to be spawned.
|
||||
pub mob_id: u32,
|
||||
/// The maximum number of mobs that can be spawned.
|
||||
pub max_mob_count: u32,
|
||||
/// The maximum number of mobs that can be spawned at once.
|
||||
pub max_spawn_count_at_once: u32,
|
||||
/// The range within which the spawner should generate a mob.
|
||||
pub spawn_range: u32,
|
||||
/// The frequency in seconds at which the spawner should generate a mob.
|
||||
pub spawn_rate: Duration,
|
||||
/// The last time a mob was spawned.
|
||||
pub last_spawn: Instant,
|
||||
/// The list of mobs that have been spawned by this spawner.
|
||||
pub mobs: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl Default for Spawner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mob_id: 0,
|
||||
max_mob_count: 1,
|
||||
max_spawn_count_at_once: 1,
|
||||
spawn_rate: Duration::from_secs(10),
|
||||
last_spawn: Instant::now(),
|
||||
spawn_range: 100,
|
||||
mobs: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spawner {
|
||||
pub fn get_mob_id(&self) -> u32 {
|
||||
self.mob_id
|
||||
}
|
||||
|
||||
pub fn get_max_mob_count(&self) -> u32 {
|
||||
self.max_mob_count
|
||||
}
|
||||
|
||||
pub fn get_max_spawn_count_at_once(&self) -> u32 {
|
||||
self.max_spawn_count_at_once
|
||||
}
|
||||
|
||||
pub fn get_spawn_rate(&self) -> Duration {
|
||||
self.spawn_rate
|
||||
}
|
||||
|
||||
pub fn get_last_spawn(&self) -> Instant {
|
||||
self.last_spawn
|
||||
}
|
||||
|
||||
pub fn get_spawn_range(&self) -> u32 {
|
||||
self.spawn_range
|
||||
}
|
||||
}
|
||||
51
game-logic-service/src/components/stats.rs
Normal file
51
game-logic-service/src/components/stats.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
#[derive(Debug)]
|
||||
pub struct Stats {
|
||||
strength: u16,
|
||||
dexterity: u16,
|
||||
intelligence: u16,
|
||||
constitution: u16,
|
||||
charisma: u16,
|
||||
sense: u16,
|
||||
head_size: u8,
|
||||
body_size: u8,
|
||||
}
|
||||
|
||||
impl Default for Stats {
|
||||
fn default() -> Self {
|
||||
Self { strength: 10, dexterity: 10, intelligence: 10, constitution: 10, charisma: 10, sense: 10, head_size: 1, body_size: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn get_strength(&self) -> u16 {
|
||||
self.strength
|
||||
}
|
||||
|
||||
pub fn get_dexterity(&self) -> u16 {
|
||||
self.dexterity
|
||||
}
|
||||
|
||||
pub fn get_intelligence(&self) -> u16 {
|
||||
self.intelligence
|
||||
}
|
||||
|
||||
pub fn get_constitution(&self) -> u16 {
|
||||
self.constitution
|
||||
}
|
||||
|
||||
pub fn get_charisma(&self) -> u16 {
|
||||
self.charisma
|
||||
}
|
||||
|
||||
pub fn get_sense(&self) -> u16 {
|
||||
self.sense
|
||||
}
|
||||
|
||||
pub fn get_head_size(&self) -> u8 {
|
||||
self.head_size
|
||||
}
|
||||
|
||||
pub fn get_body_size(&self) -> u8 {
|
||||
self.body_size
|
||||
}
|
||||
}
|
||||
20
game-logic-service/src/components/target.rs
Normal file
20
game-logic-service/src/components/target.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use hecs::{Entity};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Target {
|
||||
target: Entity,
|
||||
}
|
||||
|
||||
impl Target {
|
||||
pub fn new(target: Entity) -> Self {
|
||||
Self { target }
|
||||
}
|
||||
|
||||
pub fn get_target(&self) -> Entity {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, target: Entity) {
|
||||
self.target = target;
|
||||
}
|
||||
}
|
||||
251
game-logic-service/src/entity_factory.rs
Normal file
251
game-logic-service/src/entity_factory.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
use std::cmp::min;
|
||||
use hecs::{Entity, World};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::debug;
|
||||
use crate::components::basic_info::BasicInfo;
|
||||
use crate::components::level::Level;
|
||||
use crate::components::life::Life;
|
||||
use crate::components::position::Position;
|
||||
use crate::components::spawner::Spawner;
|
||||
use crate::components::markers::*;
|
||||
use crate::id_manager::IdManager;
|
||||
use crate::loader::{MobSpawnPoint, Vec3, Zone};
|
||||
use crate::random::{get_random_number_in_range, get_random_point_in_circle};
|
||||
|
||||
pub struct EntityFactory<'a> {
|
||||
pub world: &'a mut World,
|
||||
pub id_manager: Arc<Mutex<IdManager>>
|
||||
}
|
||||
|
||||
impl<'a> EntityFactory<'a> {
|
||||
/// Creates a new factory.
|
||||
pub fn new(world: &'a mut World) -> Self {
|
||||
Self {
|
||||
world,
|
||||
id_manager: Arc::new(Mutex::new(IdManager::new()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_map(&mut self, map: Zone) {
|
||||
// Load the map data from the database
|
||||
// Spawn all the entities in the map
|
||||
|
||||
if let Some(mob_spawn_points) = map.mob_spawn_points {
|
||||
for spawner in mob_spawn_points {
|
||||
// Process the mob spawner.
|
||||
spawner
|
||||
.normal_spawn_points
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.chain(spawner.tactical_spawn_points.unwrap_or_default().into_iter())
|
||||
.for_each(|spawn_point| {
|
||||
// debug!("Spawn Point: {:?}", spawn_point);
|
||||
self.spawn_mob_spawner(
|
||||
spawn_point.monster_id,
|
||||
spawner.limit,
|
||||
spawn_point.count,
|
||||
Position {
|
||||
x: spawner.position.x as f32,
|
||||
y: spawner.position.y as f32,
|
||||
z: spawner.position.z as f32,
|
||||
map_id: map.id as u16,
|
||||
spawn_id: 0,
|
||||
},
|
||||
Duration::from_secs(spawner.interval as u64),
|
||||
spawner.range,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(npc_spawn_points) = map.npc_spawn_points {
|
||||
for npc in npc_spawn_points {
|
||||
// Process the npc spawn point.
|
||||
// debug!("NPC ID: {}", npc.id);
|
||||
// debug!("Dialog ID: {}", npc.dialog_id);
|
||||
// debug!("Position: x = {}, y = {}, z = {}",
|
||||
// npc.position.x, npc.position.y, npc.position.z);
|
||||
self.spawn_npc(npc.id, Position {
|
||||
x: npc.position.x as f32,
|
||||
y: npc.position.y as f32,
|
||||
z: npc.position.z as f32,
|
||||
map_id: map.id as u16,
|
||||
spawn_id: 0,
|
||||
},
|
||||
npc.angle);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(spawn_points) = map.spawn_points {
|
||||
for spawn_point in spawn_points {
|
||||
// Process the spawn point.
|
||||
// debug!("Player Spawn Point Type: {}", spawn_point.point_type);
|
||||
// debug!("Position: x = {}, y = {}, z = {}",
|
||||
// spawn_point.position.x, spawn_point.position.y, spawn_point.position.z);
|
||||
self.create_player_spawn_point(spawn_point.point_type, Position {
|
||||
x: spawn_point.position.x as f32,
|
||||
y: spawn_point.position.y as f32,
|
||||
z: spawn_point.position.z as f32,
|
||||
map_id: map.id as u16,
|
||||
spawn_id: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(warp_points) = map.warp_points {
|
||||
for warp_point in warp_points {
|
||||
// Process the warp point.
|
||||
// debug!("Warp Point Alias: {}", warp_point.alias);
|
||||
// debug!("Destination Gate ID: {}", warp_point.destination_gate_id);
|
||||
// debug!("Destination: x = {}, y = {}, z = {}",
|
||||
// warp_point.destination.x, warp_point.destination.y, warp_point.destination.z);
|
||||
// debug!("Map ID: {}", warp_point.map_id);
|
||||
// debug!("Min Position: x = {}, y = {}, z = {}",
|
||||
// warp_point.min_position.x, warp_point.min_position.y, warp_point.min_position.z);
|
||||
// debug!("Max Position: x = {}, y = {}, z = {}",
|
||||
// warp_point.max_position.x, warp_point.max_position.y, warp_point.max_position.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_player_spawn_point(&mut self, point_type: u32, pos: Position) {
|
||||
self.world.spawn((PlayerSpawn {point_type},pos.clone()));
|
||||
debug!("Player spawn point created at position: {:?}", pos);
|
||||
}
|
||||
|
||||
pub fn spawn_player(&mut self, pos: Position) {
|
||||
let id = self.id_manager.lock().unwrap().get_free_id();
|
||||
let basic_info = BasicInfo {
|
||||
name: "Player".to_string(),
|
||||
id: id,
|
||||
tag: id as u32,
|
||||
..Default::default()
|
||||
};
|
||||
let level = Level { level: 1, ..Default::default() };
|
||||
|
||||
let base_hp = 100;
|
||||
let hp = (base_hp * level.level) as u32;
|
||||
let life = Life::new(hp, hp);
|
||||
|
||||
self.world.spawn((Player, basic_info, level, life, pos.clone()));
|
||||
debug!("Player spawned at position: {:?}", pos);
|
||||
}
|
||||
|
||||
/// Spawns a npc at the specified position.
|
||||
pub fn spawn_npc(&mut self, npc_id: u32, pos: Position, angle: f32) {
|
||||
let id = self.id_manager.lock().unwrap().get_free_id();
|
||||
let basic_info = BasicInfo {
|
||||
name: "NPC".to_string(),
|
||||
id: id,
|
||||
tag: id as u32,
|
||||
..Default::default()
|
||||
};
|
||||
let level = Level { level: 1, ..Default::default() };
|
||||
|
||||
let base_hp = 100;
|
||||
let hp = (base_hp * level.level) as u32;
|
||||
let life = Life::new(hp, hp);
|
||||
|
||||
self.world.spawn((Npc {id: npc_id, quest_id: 0, angle, event_status: 0}, basic_info, level, life, pos.clone()));
|
||||
debug!("Npc spawned at position: {:?}", pos);
|
||||
}
|
||||
|
||||
/// Spawns a mob at the specified position.
|
||||
pub fn spawn_mob(&mut self, mob_id: u32, spawn_range: u32, pos: Position) -> Entity {
|
||||
let id = self.id_manager.lock().unwrap().get_free_id();
|
||||
let basic_info = BasicInfo {
|
||||
name: "MOB".to_string(),
|
||||
id: id,
|
||||
tag: id as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let level = Level { level: 1, ..Default::default() };
|
||||
|
||||
let base_hp = 100;
|
||||
let hp = (base_hp * level.level) as u32;
|
||||
let life = Life::new(hp, hp);
|
||||
|
||||
let (x, y) = get_random_point_in_circle((pos.x, pos.y), spawn_range as f32);
|
||||
let spawn_point = Position { x, y, z: pos.z, map_id: pos.map_id, spawn_id: pos.spawn_id };
|
||||
|
||||
// Spawn the mob.
|
||||
let entity = self.world.spawn((Mob {id: mob_id, quest_id: 0}, basic_info, spawn_point.clone()));
|
||||
entity
|
||||
}
|
||||
|
||||
/// Spawns a spawner at the specified position with the given spawn rate.
|
||||
pub fn spawn_mob_spawner(&mut self, mob_id: u32, max_mob_count: u32, max_spawn_count_at_once: u32, pos: Position, spawn_rate: Duration, spawn_range: u32) {
|
||||
let spawner = Spawner {
|
||||
mob_id,
|
||||
spawn_rate,
|
||||
spawn_range,
|
||||
max_mob_count,
|
||||
max_spawn_count_at_once,
|
||||
..Default::default()
|
||||
};
|
||||
self.world.spawn((spawner, pos.clone()));
|
||||
}
|
||||
|
||||
/// Updates all spawner entities in the world.
|
||||
///
|
||||
/// If the spawn interval has elapsed, a mob will be spawned and the spawner's
|
||||
/// last spawn timestamp is updated.
|
||||
pub fn update_spawners(&mut self) {
|
||||
let now = Instant::now();
|
||||
|
||||
// Collect spawner entities to avoid borrow issues.
|
||||
// We need to clone the Position since we use it after.
|
||||
let spawner_data: Vec<(hecs::Entity, Position, Spawner)> = self
|
||||
.world
|
||||
.query::<(&Position, &Spawner)>()
|
||||
.iter()
|
||||
.map(|(entity, (pos, spawner))| {
|
||||
(
|
||||
entity,
|
||||
pos.clone(),
|
||||
Spawner {
|
||||
mob_id: spawner.mob_id,
|
||||
max_mob_count: spawner.max_mob_count,
|
||||
max_spawn_count_at_once: spawner.max_spawn_count_at_once,
|
||||
spawn_rate: spawner.spawn_rate,
|
||||
last_spawn: spawner.last_spawn,
|
||||
spawn_range: spawner.spawn_range,
|
||||
mobs: spawner.mobs.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Iterate over each spawner and check if it's time to spawn a mob.
|
||||
for (entity, pos, spawner) in spawner_data {
|
||||
let mut mob_list = spawner.mobs.clone();
|
||||
if mob_list.len() >= spawner.max_mob_count as usize {
|
||||
continue;
|
||||
}
|
||||
if now.duration_since(spawner.last_spawn) >= spawner.spawn_rate {
|
||||
let spawn_count = get_random_number_in_range(0, min(spawner.max_spawn_count_at_once, (spawner.max_mob_count - spawner.mobs.len() as u32)));
|
||||
for _ in 0..spawn_count {
|
||||
let mob_entity = self.spawn_mob(spawner.mob_id, spawner.spawn_range, pos.clone());
|
||||
|
||||
// Add the mob to the spawner's list of mobs.
|
||||
mob_list.push(mob_entity);
|
||||
|
||||
//TODO: Send a packet to all clients in the area to inform them of the new mob
|
||||
}
|
||||
|
||||
// Update the spawner's last_spawn time in the world.
|
||||
let mut query = self.world.query_one::<(&Position, &mut Spawner)>(entity).unwrap();
|
||||
let (_pos, spawner_mut) = query.get().unwrap();
|
||||
spawner_mut.last_spawn = now;
|
||||
spawner_mut.mobs = mob_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
self.update_spawners();
|
||||
}
|
||||
}
|
||||
31
game-logic-service/src/game_logic_service.rs
Normal file
31
game-logic-service/src/game_logic_service.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tonic::{Request, Response, Status};
|
||||
use tonic::metadata::MetadataMap;
|
||||
use tracing::debug;
|
||||
|
||||
pub mod game_logic {
|
||||
tonic::include_proto!("game_logic");
|
||||
}
|
||||
|
||||
use game_logic::game_logic_service_server::GameLogicService;
|
||||
use crate::game_logic_service::game_logic::{NearbyObjectsRequest, NearbyObjectsResponse};
|
||||
|
||||
pub struct MyGameLogicService {
|
||||
pub map_id: u32,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl GameLogicService for MyGameLogicService {
|
||||
async fn get_nearby_objects(&self, request: Request<NearbyObjectsRequest>) -> Result<Response<NearbyObjectsResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
debug!("{:?}", req);
|
||||
|
||||
let response = NearbyObjectsResponse {
|
||||
objects: vec![],
|
||||
};
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
}
|
||||
69
game-logic-service/src/game_service.rs
Normal file
69
game-logic-service/src/game_service.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use std::pin::Pin;
|
||||
use futures::{Stream, StreamExt};
|
||||
use tonic::{Request, Response, Status, Streaming};
|
||||
use tracing::{info, error};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub mod character_common {
|
||||
tonic::include_proto!("character_common");
|
||||
}
|
||||
pub mod game {
|
||||
tonic::include_proto!("game");
|
||||
}
|
||||
|
||||
use game::event_service_server::{EventService, EventServiceServer};
|
||||
use game::GenericEvent;
|
||||
|
||||
pub struct MyGameService {}
|
||||
|
||||
impl MyGameService {
|
||||
pub fn into_service(self) -> EventServiceServer<Self> {
|
||||
EventServiceServer::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl EventService for MyGameService {
|
||||
type StreamEventsStream =
|
||||
Pin<Box<dyn Stream<Item = Result<GenericEvent, Status>> + Send + Sync + 'static>>;
|
||||
|
||||
async fn stream_events(
|
||||
&self,
|
||||
request: Request<Streaming<GenericEvent>>,
|
||||
) -> Result<Response<Self::StreamEventsStream>, Status> {
|
||||
info!("Received connection from world service without authentication");
|
||||
|
||||
// Extract the inbound stream.
|
||||
let mut inbound_stream = request.into_inner();
|
||||
|
||||
// Create a channel for sending outgoing events.
|
||||
let (tx, rx) = mpsc::channel(32);
|
||||
|
||||
// Spawn a task to handle processing of inbound events.
|
||||
let tx_clone = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(event) = inbound_stream.next().await {
|
||||
match event {
|
||||
Ok(ev) => {
|
||||
info!("Received event: {:?}", ev);
|
||||
// Process the event as needed.
|
||||
// For demonstration, simply echo the event back.
|
||||
if let Err(err) = tx_clone.send(ev).await {
|
||||
error!("Failed forwarding event: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error receiving event: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Inbound event stream ended.");
|
||||
});
|
||||
|
||||
// Wrap the receiver in a stream and return it.
|
||||
let outbound_stream = ReceiverStream::new(rx).map(|msg| Ok(msg));
|
||||
Ok(Response::new(Box::pin(outbound_stream) as Self::StreamEventsStream))
|
||||
}
|
||||
}
|
||||
61
game-logic-service/src/id_manager.rs
Normal file
61
game-logic-service/src/id_manager.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IdManager {
|
||||
free_ids: HashSet<u16>,
|
||||
max_id: u16, // the first ID is 1
|
||||
}
|
||||
|
||||
impl IdManager {
|
||||
/// Creates a new IdManager with no free IDs and starting ID of 1.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
free_ids: HashSet::new(),
|
||||
max_id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves an available ID.
|
||||
///
|
||||
/// If any are available in the free_ids set, returns one of them.
|
||||
/// Otherwise, returns a fresh ID and increments max_id.
|
||||
pub fn get_free_id(&mut self) -> u16 {
|
||||
if let Some(&id) = self.free_ids.iter().next() {
|
||||
self.free_ids.remove(&id);
|
||||
id
|
||||
} else {
|
||||
let id = self.max_id;
|
||||
self.max_id += 1;
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases an ID, making it available for reuse.
|
||||
pub fn release_id(&mut self, id: u16) {
|
||||
self.free_ids.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_id_manager() {
|
||||
let mut manager = IdManager::new();
|
||||
|
||||
let id1 = manager.get_free_id();
|
||||
let id2 = manager.get_free_id();
|
||||
assert_eq!(id1, 1);
|
||||
assert_eq!(id2, 2);
|
||||
|
||||
// Release id1 and then get a free id which should return id1
|
||||
manager.release_id(id1);
|
||||
let id3 = manager.get_free_id();
|
||||
assert_eq!(id3, id1);
|
||||
|
||||
// Next free id should be id3 (old id2 was already used)
|
||||
let id4 = manager.get_free_id();
|
||||
assert_eq!(id4, 3);
|
||||
}
|
||||
}
|
||||
160
game-logic-service/src/loader.rs
Normal file
160
game-logic-service/src/loader.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
// src/loader.rs
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::BufReader,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
/// A 3D vector type used for positions, scales, etc.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Vec3 {
|
||||
#[serde(rename = "X")]
|
||||
pub x: f64,
|
||||
#[serde(rename = "Y")]
|
||||
pub y: f64,
|
||||
#[serde(rename = "Z")]
|
||||
pub z: f64,
|
||||
}
|
||||
|
||||
/// Top-level zone definition.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Zone {
|
||||
#[serde(rename = "Id")]
|
||||
pub id: u32,
|
||||
#[serde(rename = "SpawnPoints")]
|
||||
pub spawn_points: Option<Vec<SpawnPoint>>,
|
||||
#[serde(rename = "NpcSpawnPoints")]
|
||||
pub npc_spawn_points: Option<Vec<NpcSpawnPoint>>,
|
||||
#[serde(rename = "MobSpawnPoints")]
|
||||
pub mob_spawn_points: Option<Vec<MobSpawnPoint>>,
|
||||
#[serde(rename = "WarpPoints")]
|
||||
pub warp_points: Option<Vec<WarpPoint>>,
|
||||
}
|
||||
|
||||
/// A simple spawn point with a type and a position.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SpawnPoint {
|
||||
#[serde(rename = "Type")]
|
||||
pub point_type: u32,
|
||||
#[serde(rename = "Position")]
|
||||
pub position: Vec3,
|
||||
}
|
||||
|
||||
/// An NPC spawn point.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NpcSpawnPoint {
|
||||
#[serde(rename = "Id")]
|
||||
pub id: u32,
|
||||
#[serde(rename = "DialogId")]
|
||||
pub dialog_id: u32,
|
||||
#[serde(rename = "Position")]
|
||||
pub position: Vec3,
|
||||
#[serde(rename = "Angle")]
|
||||
pub angle: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SpawnPointDefinition {
|
||||
#[serde(rename = "Name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "Monster")]
|
||||
pub monster_id: u32,
|
||||
#[serde(rename = "Count")]
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
/// A mob spawn point.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MobSpawnPoint {
|
||||
#[serde(rename = "SpawnName")]
|
||||
pub spawn_name: String,
|
||||
#[serde(rename = "NormalSpawnPoints")]
|
||||
pub normal_spawn_points: Option<Vec<SpawnPointDefinition>>,
|
||||
#[serde(rename = "TacticalSpawnPoints")]
|
||||
pub tactical_spawn_points: Option<Vec<SpawnPointDefinition>>,
|
||||
#[serde(rename = "Interval")]
|
||||
pub interval: u32,
|
||||
#[serde(rename = "Limit")]
|
||||
pub limit: u32,
|
||||
#[serde(rename = "Range")]
|
||||
pub range: u32,
|
||||
#[serde(rename = "TacticalVariable")]
|
||||
pub tactical_variable: u32,
|
||||
#[serde(rename = "Name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "ObjectType")]
|
||||
pub object_type: u32,
|
||||
#[serde(rename = "ObjectID")]
|
||||
pub object_id: u32,
|
||||
#[serde(rename = "MapPosition")]
|
||||
pub map_position: Option<MapPosition>,
|
||||
#[serde(rename = "Position")]
|
||||
pub position: Vec3,
|
||||
#[serde(rename = "Rotation")]
|
||||
pub rotation: Rotation,
|
||||
#[serde(rename = "Scale")]
|
||||
pub scale: Vec3,
|
||||
}
|
||||
|
||||
/// A simple map position.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MapPosition {
|
||||
#[serde(rename = "X")]
|
||||
pub x: i32,
|
||||
#[serde(rename = "Y")]
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
/// A simple rotation structure.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Rotation {
|
||||
#[serde(rename = "W")]
|
||||
pub w: f64,
|
||||
}
|
||||
|
||||
/// A warp point.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WarpPoint {
|
||||
#[serde(rename = "Alias")]
|
||||
pub alias: String,
|
||||
#[serde(rename = "DestinationGateId")]
|
||||
pub destination_gate_id: u32,
|
||||
#[serde(rename = "Destination")]
|
||||
pub destination: Vec3,
|
||||
#[serde(rename = "MapId")]
|
||||
pub map_id: u32,
|
||||
#[serde(rename = "MinPosition")]
|
||||
pub min_position: Vec3,
|
||||
#[serde(rename = "MaxPosition")]
|
||||
pub max_position: Vec3,
|
||||
}
|
||||
|
||||
/// Loads only the zone (from a JSON file) with a top-level `Id` matching the given `map_id`.
|
||||
///
|
||||
/// The JSON file is assumed to be an array of zones.
|
||||
pub fn load_zone_for_map(
|
||||
file_path: &str,
|
||||
map_id: u32,
|
||||
) -> Result<Option<Zone>, Box<dyn Error>> {
|
||||
let file = File::open(file_path)?;
|
||||
let reader = BufReader::new(file);
|
||||
// Deserialize the file as a JSON value first.
|
||||
let value: Value = serde_json::from_reader(reader)?;
|
||||
// Ensure the top-level element is an array.
|
||||
let zones = value
|
||||
.as_array()
|
||||
.ok_or("Expected JSON array at top level")?;
|
||||
|
||||
for zone_value in zones {
|
||||
// Deserialize each zone.
|
||||
let zone: Zone = serde_json::from_value(zone_value.clone())?;
|
||||
if zone.id == map_id {
|
||||
return Ok(Some(zone));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
85
game-logic-service/src/main.rs
Normal file
85
game-logic-service/src/main.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
pub mod components;
|
||||
mod entity_factory;
|
||||
mod id_manager;
|
||||
mod loader;
|
||||
mod random;
|
||||
mod game_logic_service;
|
||||
mod game_service;
|
||||
|
||||
use dotenv::dotenv;
|
||||
use std::env;
|
||||
use hecs::World;
|
||||
use tracing::{debug, error, info};
|
||||
use utils::service_discovery::{get_kube_service_endpoints_by_dns, get_service_endpoints_by_dns};
|
||||
use utils::{health_check, logging};
|
||||
|
||||
use loader::load_zone_for_map;
|
||||
use crate::components::position::Position;
|
||||
use crate::entity_factory::EntityFactory;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dotenv().ok();
|
||||
let app_name = env!("CARGO_PKG_NAME");
|
||||
logging::setup_logging(app_name, &["game_logic_service", "health_check"]);
|
||||
|
||||
|
||||
let map_id = env::var("MAP_ID").unwrap_or_else(|_| "20".to_string()).parse::<u32>()?;
|
||||
let file_path = "/opt/data/zone_data.json";
|
||||
|
||||
// // Set the gRPC server address
|
||||
let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0".to_string());
|
||||
let port = env::var("SERVICE_PORT").unwrap_or_else(|_| "50056".to_string());
|
||||
let db_url = format!(
|
||||
"http://{}",
|
||||
get_kube_service_endpoints_by_dns("database-service", "tcp", "database-service")
|
||||
.await?
|
||||
.get(0)
|
||||
.unwrap()
|
||||
);
|
||||
let chat_service = format!(
|
||||
"http://{}",
|
||||
get_kube_service_endpoints_by_dns("chat-service", "tcp", "chat-service")
|
||||
.await?
|
||||
.get(0)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let game_logic_task = tokio::spawn(async move {
|
||||
let mut loading = true;
|
||||
let mut world = World::new();
|
||||
let mut factory = EntityFactory::new(&mut world);
|
||||
|
||||
loop {
|
||||
// Load the map
|
||||
if loading {
|
||||
match load_zone_for_map(file_path, map_id) {
|
||||
Ok(Some(zone)) => {
|
||||
info!("Zone with Map Id {} found:", map_id);
|
||||
factory.load_map(zone);
|
||||
loading = false;
|
||||
}
|
||||
Ok(None) => {
|
||||
error!("No zone found for Map Id {}", map_id);
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error loading zone: {}", e);
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the world
|
||||
if !loading {
|
||||
factory.run();
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1000/30));
|
||||
}
|
||||
});
|
||||
|
||||
// Register service with Consul
|
||||
// health_check::start_health_check(addr.as_str()).await?;
|
||||
utils::signal_handler::wait_for_signal().await;
|
||||
Ok(())
|
||||
}
|
||||
37
game-logic-service/src/random.rs
Normal file
37
game-logic-service/src/random.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use rand::Rng;
|
||||
|
||||
pub fn get_random_number() -> u32 {
|
||||
rand::thread_rng().gen_range(0..1000000)
|
||||
}
|
||||
|
||||
pub fn get_random_number_in_range(min: u32, max: u32) -> u32 {
|
||||
rand::thread_rng().gen_range(min..=max)
|
||||
}
|
||||
|
||||
pub fn get_random_number_in_range_f32(min: f32, max: f32) -> f32 {
|
||||
rand::thread_rng().gen_range(min..=max)
|
||||
}
|
||||
|
||||
pub fn get_random_number_in_range_f64(min: f64, max: f64) -> f64 {
|
||||
rand::thread_rng().gen_range(min..=max)
|
||||
}
|
||||
|
||||
pub fn get_random_bool() -> bool {
|
||||
rand::thread_rng().gen_bool(0.5)
|
||||
}
|
||||
|
||||
pub fn get_random_item_from_vec<T>(vec: &Vec<T>) -> Option<&T> {
|
||||
if vec.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let index = rand::thread_rng().gen_range(0..vec.len());
|
||||
vec.get(index)
|
||||
}
|
||||
|
||||
pub fn get_random_point_in_circle(center: (f32, f32), radius: f32) -> (f32, f32) {
|
||||
let angle = rand::thread_rng().gen_range(0.0..std::f32::consts::PI * 2.0);
|
||||
let distance = rand::thread_rng().gen_range(0.0..radius);
|
||||
let x = center.0 + distance * angle.cos();
|
||||
let y = center.1 + distance * angle.sin();
|
||||
(x, y)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ fn main() {
|
||||
"../proto/chat.proto",
|
||||
"../proto/character.proto",
|
||||
"../proto/character_common.proto",
|
||||
"../proto/game.proto"
|
||||
],
|
||||
&["../proto"],
|
||||
)
|
||||
|
||||
167
proto/game.proto
Normal file
167
proto/game.proto
Normal file
@@ -0,0 +1,167 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
//google.protobuf.Empty
|
||||
import "character_common.proto";
|
||||
|
||||
package game;
|
||||
|
||||
// Define a mob spawn event.
|
||||
message MobSpawnEvent {
|
||||
uint32 id = 1;
|
||||
float pos_x = 2;
|
||||
float pos_y = 3;
|
||||
float dest_pos_x = 4;
|
||||
float dest_pos_y = 5;
|
||||
uint32 command = 6;
|
||||
uint32 target_id = 7;
|
||||
uint32 move_mode = 8;
|
||||
int32 hp = 9;
|
||||
int32 team_id = 10;
|
||||
uint32 status_flag = 11;
|
||||
uint32 npc_id = 12;
|
||||
uint32 quest_id = 13;
|
||||
}
|
||||
|
||||
message NpcSpawnEvent {
|
||||
uint32 id = 1;
|
||||
float pos_x = 2;
|
||||
float pos_y = 3;
|
||||
float dest_pos_x = 4;
|
||||
float dest_pos_y = 5;
|
||||
uint32 command = 6;
|
||||
uint32 target_id = 7;
|
||||
uint32 move_mode = 8;
|
||||
int32 hp = 9;
|
||||
int32 team_id = 10;
|
||||
uint32 status_flag = 11;
|
||||
uint32 npc_id = 12;
|
||||
uint32 quest_id = 13;
|
||||
float angle = 14;
|
||||
uint32 event_status = 15;
|
||||
}
|
||||
|
||||
message PlayerSpawnEvent {
|
||||
uint32 id = 1;
|
||||
float pos_x = 2;
|
||||
float pos_y = 3;
|
||||
float dest_pos_x = 4;
|
||||
float dest_pos_y = 5;
|
||||
uint32 command = 6;
|
||||
uint32 target_id = 7;
|
||||
uint32 move_mode = 8;
|
||||
int32 hp = 9;
|
||||
int32 team_id = 10;
|
||||
uint32 status_flag = 11;
|
||||
uint32 race = 12;
|
||||
uint32 run_speed = 13;
|
||||
uint32 atk_speed = 14;
|
||||
uint32 weight_rate = 15;
|
||||
uint32 face = 16;
|
||||
uint32 hair = 17;
|
||||
repeated character_common.EquippedItem inventory = 18;
|
||||
repeated character_common.ItemHeader bullets = 19;
|
||||
repeated character_common.EquippedItem riding_items = 20;
|
||||
uint32 job = 21;
|
||||
uint32 level = 22;
|
||||
uint32 z = 23;
|
||||
uint32 sub_flag = 24;
|
||||
string name = 25;
|
||||
string other_name = 26;
|
||||
}
|
||||
|
||||
message ObjectDespawnEvent {
|
||||
uint32 object_id = 1;
|
||||
}
|
||||
|
||||
// Define a player connect event.
|
||||
message PlayerConnectEvent {
|
||||
string player_id = 1;
|
||||
}
|
||||
|
||||
// Define a player disconnect event.
|
||||
message PlayerDisconnectEvent {
|
||||
string player_id = 1;
|
||||
}
|
||||
|
||||
message PlayerMoveEvent {
|
||||
string player_id = 1;
|
||||
float pos_x = 2;
|
||||
float pos_y = 3;
|
||||
}
|
||||
|
||||
message PlayerAttackEvent {
|
||||
string player_id = 1;
|
||||
uint32 target_id = 2;
|
||||
}
|
||||
|
||||
message PlayerSkillEvent {
|
||||
string player_id = 1;
|
||||
uint32 skill_id = 2;
|
||||
uint32 target_id = 3;
|
||||
}
|
||||
|
||||
message MobAttackEvent {
|
||||
uint32 mob_id = 1;
|
||||
uint32 target_id = 2;
|
||||
}
|
||||
|
||||
message MobSkillEvent {
|
||||
uint32 mob_id = 1;
|
||||
uint32 skill_id = 2;
|
||||
uint32 target_id = 3;
|
||||
}
|
||||
|
||||
message DamageEvent {
|
||||
uint32 source_id = 1;
|
||||
uint32 target_id = 2;
|
||||
uint32 damage = 3;
|
||||
uint32 action = 4;
|
||||
float x = 5;
|
||||
float y = 6;
|
||||
character_common.Item item = 7;
|
||||
uint32 id = 8;
|
||||
uint32 owner_id = 9;
|
||||
}
|
||||
|
||||
message DropItemEvent {
|
||||
float x = 1;
|
||||
float y = 2;
|
||||
character_common.Item item = 3;
|
||||
uint32 id = 4;
|
||||
uint32 owner_id = 5;
|
||||
uint32 time_to_live = 6;
|
||||
}
|
||||
|
||||
message PlayerLevelUpEvent {
|
||||
string player_id = 1;
|
||||
uint32 level = 2;
|
||||
uint32 exp = 3;
|
||||
uint32 stat_points = 4;
|
||||
uint32 skill_points = 5;
|
||||
}
|
||||
|
||||
// Define a generic event using oneof for the different types.
|
||||
message GenericEvent {
|
||||
repeated string clients = 1; // List of clients to get this event.
|
||||
|
||||
oneof event {
|
||||
MobSpawnEvent mob_spawn = 2;
|
||||
NpcSpawnEvent npc_spawn = 3;
|
||||
PlayerSpawnEvent player_spawn = 4;
|
||||
PlayerMoveEvent player_move = 5;
|
||||
PlayerAttackEvent player_attack = 6;
|
||||
PlayerSkillEvent player_skill = 7;
|
||||
MobAttackEvent mob_attack = 8;
|
||||
MobSkillEvent mob_skill = 9;
|
||||
DamageEvent damage = 10;
|
||||
ObjectDespawnEvent object_despawn = 11;
|
||||
DropItemEvent drop_item = 12;
|
||||
PlayerLevelUpEvent player_level_up = 13;
|
||||
}
|
||||
}
|
||||
|
||||
// gRPC service definition for a streaming RPC.
|
||||
service EventService {
|
||||
rpc StreamEvents(stream GenericEvent) returns (stream GenericEvent);
|
||||
}
|
||||
@@ -3,50 +3,25 @@ syntax = "proto3";
|
||||
package game_logic;
|
||||
|
||||
service GameLogicService {
|
||||
rpc GetCharacter(CharacterRequest) returns (CharacterResponse);
|
||||
rpc MoveCharacter(CharacterMoveRequest) returns (CharacterMoveResponse);
|
||||
rpc GetTargetHp(ObjectHpRequest) returns (ObjectHpResponse);
|
||||
rpc GetNearbyObjects(NearbyObjectsRequest) returns (NearbyObjectsResponse);
|
||||
}
|
||||
|
||||
message CharacterRequest {
|
||||
string token = 1;
|
||||
string user_id = 2;
|
||||
string char_id = 3;
|
||||
string session_id = 4;
|
||||
}
|
||||
|
||||
message CharacterResponse {
|
||||
int32 count = 1;
|
||||
}
|
||||
|
||||
message CharacterMoveRequest {
|
||||
message NearbyObjectsRequest {
|
||||
string session_id = 1;
|
||||
uint32 target_id = 2;
|
||||
float x = 2;
|
||||
float y = 3;
|
||||
float z = 4;
|
||||
int32 map_id = 5;
|
||||
}
|
||||
|
||||
message NearbyObjectsResponse {
|
||||
repeated Object objects = 1;
|
||||
}
|
||||
|
||||
message Object {
|
||||
int32 id = 1;
|
||||
int32 type_ = 2;
|
||||
float x = 3;
|
||||
float y = 4;
|
||||
float z = 5;
|
||||
}
|
||||
|
||||
message CharacterMoveResponse {
|
||||
int32 id = 1;
|
||||
int32 target_id = 2;
|
||||
int32 distance = 3;
|
||||
float x = 4;
|
||||
float y = 5;
|
||||
float z = 6;
|
||||
}
|
||||
|
||||
message AttackRequest {
|
||||
string session_id = 1;
|
||||
uint32 target_id = 2;
|
||||
}
|
||||
|
||||
message ObjectHpRequest {
|
||||
string session_id = 1;
|
||||
uint32 target_id = 2;
|
||||
}
|
||||
|
||||
message ObjectHpResponse {
|
||||
uint32 target_id = 1;
|
||||
int32 hp = 2;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ images = [
|
||||
"chat-service",
|
||||
"character-service",
|
||||
"database-service",
|
||||
"game-logic-service",
|
||||
"packet-service",
|
||||
"world-service"
|
||||
]
|
||||
@@ -15,6 +16,7 @@ dockerfile_paths = [
|
||||
"../chat-service/Dockerfile",
|
||||
"../character-service/Dockerfile",
|
||||
"../database-service/Dockerfile",
|
||||
"../game-logic-service/Dockerfile",
|
||||
"../packet-service/Dockerfile",
|
||||
"../world-service/Dockerfile",
|
||||
]
|
||||
|
||||
@@ -4,13 +4,13 @@ fn main() {
|
||||
.build_server(true) // Generate gRPC server code
|
||||
.compile_well_known_types(true)
|
||||
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
|
||||
.compile_protos(&["../proto/world.proto"], &["../proto"])
|
||||
.compile_protos(&["../proto/world.proto", "../proto/game.proto"], &["../proto"])
|
||||
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
||||
|
||||
// gRPC Client code
|
||||
tonic_build::configure()
|
||||
.build_server(false) // Generate gRPC client code
|
||||
.compile_well_known_types(true)
|
||||
.compile_protos(&["../proto/user_db_api.proto", "../proto/auth.proto", "../proto/character.proto", "../proto/character_common.proto", "../proto/chat.proto", "../proto/game_logic.proto"], &["../proto"])
|
||||
.compile_protos(&["../proto/user_db_api.proto", "../proto/auth.proto", "../proto/character.proto", "../proto/character_common.proto", "../proto/chat.proto"], &["../proto"])
|
||||
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user