Merge branch 'singleplayer' into 'master'

Start Singleplayer server when singleplayer is selected

See merge request veloren/veloren!39

Former-commit-id: ac8c9f3f52871d89847fdecc74ac58bb593e762b
This commit is contained in:
Joshua Barretto 2019-04-17 15:31:02 +00:00
commit 1e4d02a5d8
9 changed files with 125 additions and 6 deletions

1
Cargo.lock generated
View File

@ -1960,6 +1960,7 @@ dependencies = [
"vek 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
"veloren-client 0.2.0",
"veloren-common 0.2.0",
"veloren-server 0.2.0",
"winit 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

1
voxygen/.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
settings.toml

View File

@ -12,6 +12,7 @@ default = ["gl"]
[dependencies]
common = { package = "veloren-common", path = "../common" }
client = { package = "veloren-client", path = "../client" }
server = { package = "veloren-server", path = "../server" }
# Graphics
gfx = "0.17"

View File

@ -13,12 +13,14 @@ pub mod session;
pub mod ui;
pub mod window;
pub mod settings;
pub mod singleplayer;
// Reexports
pub use crate::error::Error;
// Standard
use std::mem;
use std::thread;
// Library
use log;
@ -28,7 +30,8 @@ use pretty_env_logger;
use crate::{
menu::main::MainMenuState,
window::Window,
settings::Settings
settings::Settings,
singleplayer::Singleplayer,
};
/// The URL of the default public server that Voxygen will connect to
@ -38,6 +41,7 @@ const DEFAULT_PUBLIC_SERVER: &'static str = "server.veloren.net";
pub struct GlobalState {
settings: Settings,
window: Window,
singleplayer: Option<Singleplayer>,
}
impl GlobalState {
@ -91,6 +95,7 @@ fn main() {
let mut global_state = GlobalState {
settings,
window,
singleplayer: None,
};
// Set up the initial play state

View File

@ -45,7 +45,10 @@ impl PlayState for CharSelectionState {
// Handle window events
for event in global_state.window.fetch_events() {
match event {
Event::Close => return PlayStateResult::Shutdown,
Event::Close => {
global_state.singleplayer = None;
return PlayStateResult::Shutdown;
},
// Pass events to ui
Event::Ui(event) => {
self.char_selection_ui.handle_event(event);
@ -60,7 +63,10 @@ impl PlayState for CharSelectionState {
// Maintain the UI
for event in self.char_selection_ui.maintain(global_state.window.renderer_mut()) {
match event {
ui::Event::Logout => return PlayStateResult::Pop,
ui::Event::Logout => {
global_state.singleplayer = None;
return PlayStateResult::Pop;
},
ui::Event::Play => return PlayStateResult::Push(
Box::new(SessionState::new(&mut global_state.window, self.client.clone()))
),

View File

@ -5,10 +5,12 @@ use super::char_selection::CharSelectionState;
use crate::{
window::{Event, Window},
GlobalState, PlayState, PlayStateResult,
singleplayer::Singleplayer,
};
use client_init::{ClientInit, Error as InitError};
use common::{clock::Clock, comp};
use std::time::Duration;
use std::thread;
use ui::{Event as MainMenuEvent, MainMenuUi};
use vek::*;
@ -100,6 +102,9 @@ impl PlayState for MainMenuState {
),
)));
}
MainMenuEvent::StartSingleplayer => {
global_state.singleplayer = Some(Singleplayer::new());
}
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
}
}

View File

@ -92,6 +92,7 @@ pub enum Event {
username: String,
server_address: String,
},
StartSingleplayer,
Quit,
}
@ -173,6 +174,18 @@ impl MainMenuUi {
});
};
}
macro_rules! singleplayer {
() => {
self.login_error = None;
events.push(Event::StartSingleplayer);
events.push(Event::LoginAttempt {
username: "singleplayer".to_string(),
server_address: "localhost".to_string(),
});
};
}
const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0);
// Username
// TODO: get a lower resolution and cleaner input_bg.png
@ -297,7 +310,7 @@ impl MainMenuUi {
.set(self.ids.singleplayer_button, ui_widgets)
.was_clicked()
{
login!();
singleplayer!();
}
// Quit
if Button::image(self.imgs.button)

View File

@ -125,7 +125,10 @@ impl PlayState for SessionState {
continue;
}
let _handled = match event {
Event::Close => return PlayStateResult::Shutdown,
Event::Close => {
global_state.singleplayer = None;
return PlayStateResult::Shutdown;
},
// Toggle cursor grabbing
Event::KeyDown(Key::ToggleCursor) => {
global_state
@ -166,7 +169,10 @@ impl PlayState for SessionState {
self.client.borrow_mut().send_chat(msg);
},
HudEvent::Logout => return PlayStateResult::Pop,
HudEvent::Quit => return PlayStateResult::Shutdown,
HudEvent::Quit => {
global_state.singleplayer = None;
return PlayStateResult::Shutdown;
},
}
}

View File

@ -0,0 +1,81 @@
use std::time::Duration;
use log::info;
use server::{Input, Event, Server};
use common::clock::Clock;
use std::{
thread,
thread::JoinHandle
};
use std::sync::mpsc::{
channel, Receiver, Sender, TryRecvError,
};
const TPS: u64 = 30;
enum Msg {
Stop,
}
/// Used to start and stop the background thread running the server
/// when in singleplayer mode.
pub struct Singleplayer {
server_thread: JoinHandle<()>,
sender: Sender<Msg>,
}
impl Singleplayer {
pub fn new() -> Self {
let (sender, reciever) = channel();
let thread = thread::spawn(move || {
run_server(reciever);
});
Singleplayer {
server_thread: thread,
sender,
}
}
}
impl Drop for Singleplayer {
fn drop(&mut self) {
self.sender.send(Msg::Stop);
}
}
fn run_server(rec: Receiver<Msg>) {
info!("Starting server-cli...");
// Set up an fps clock
let mut clock = Clock::new();
// Create server
let mut server = Server::new()
.expect("Failed to create server instance");
loop {
let events = server.tick(Input::default(), clock.get_last_delta())
.expect("Failed to tick server");
for event in events {
match event {
Event::ClientConnected { entity } => info!("Client connected!"),
Event::ClientDisconnected { entity } => info!("Client disconnected!"),
Event::Chat { entity, msg } => info!("[Client] {}", msg),
}
}
// Clean up the server after a tick
server.cleanup();
match rec.try_recv() {
Ok(msg) => break,
Err(err) => match err {
TryRecvError::Empty => (),
TryRecvError::Disconnected => break,
},
}
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / TPS));
}
}