use bincode::{deserialize, serialize};
use rand::prelude::{thread_rng, Rng, ThreadRng};
use rusty_sword_arena::{
game::{
Floatable, GameControlMsg, GameSettings, GameState, HighScores, PlayerEvent, PlayerInput,
PlayerState,
},
gfx::{clamp_vec_to_magnitude, distance, new_in_square, Color, Vec2},
net, timer,
};
use std::collections::HashMap;
use std::thread;
use std::time::{Duration, Instant};
use zmq;
struct ColorPicker {
index: usize,
colors: Vec<Color>,
}
#[allow(clippy::excessive_precision, clippy::unreadable_literal)]
impl ColorPicker {
fn new() -> Self {
let colors = vec![
Color {
r: 0.6274510,
g: 0.5176471,
b: 0.2666667,
},
Color {
r: 0.6901961,
g: 0.6901961,
b: 0.6901961,
},
Color {
r: 0.7215686,
g: 0.7215686,
b: 0.2509804,
},
Color {
r: 0.7372549,
g: 0.5490196,
b: 0.2980392,
},
Color {
r: 0.8156863,
g: 0.5019608,
b: 0.3607843,
},
Color {
r: 0.8156863,
g: 0.4392157,
b: 0.4392157,
},
Color {
r: 0.7529412,
g: 0.4392157,
b: 0.6901961,
},
Color {
r: 0.6274510,
g: 0.4392157,
b: 0.8000000,
},
Color {
r: 0.4862745,
g: 0.4392157,
b: 0.8156863,
},
Color {
r: 0.4078431,
g: 0.4549020,
b: 0.8156863,
},
Color {
r: 0.4078431,
g: 0.5333334,
b: 0.8000000,
},
Color {
r: 0.4078431,
g: 0.6117647,
b: 0.7529412,
},
Color {
r: 0.4078431,
g: 0.7058824,
b: 0.5803922,
},
Color {
r: 0.4549020,
g: 0.7058824,
b: 0.4549020,
},
Color {
r: 0.5176471,
g: 0.7058824,
b: 0.4078431,
},
Color {
r: 0.6117647,
g: 0.6588235,
b: 0.3921569,
},
Color {
r: 0.8156863,
g: 0.7058824,
b: 0.4235294,
},
Color {
r: 0.8627451,
g: 0.8627451,
b: 0.8627451,
},
Color {
r: 0.9098039,
g: 0.9098039,
b: 0.3607843,
},
Color {
r: 0.8627451,
g: 0.7058824,
b: 0.4078431,
},
Color {
r: 0.9254902,
g: 0.6588235,
b: 0.5019608,
},
Color {
r: 0.9254902,
g: 0.6274510,
b: 0.6274510,
},
Color {
r: 0.8627451,
g: 0.6117647,
b: 0.8156863,
},
Color {
r: 0.7686275,
g: 0.6117647,
b: 0.9254902,
},
Color {
r: 0.6588235,
g: 0.6274510,
b: 0.9254902,
},
Color {
r: 0.5647059,
g: 0.6431373,
b: 0.9254902,
},
Color {
r: 0.5647059,
g: 0.7058824,
b: 0.9254902,
},
Color {
r: 0.5647059,
g: 0.8000000,
b: 0.9098039,
},
Color {
r: 0.5647059,
g: 0.8941177,
b: 0.7529412,
},
Color {
r: 0.6431373,
g: 0.8941177,
b: 0.6431373,
},
Color {
r: 0.7058824,
g: 0.8941177,
b: 0.5647059,
},
Color {
r: 0.8000000,
g: 0.8313726,
b: 0.5333334,
},
Color {
r: 0.7215686,
g: 0.6117647,
b: 0.3450980,
},
Color {
r: 0.7843137,
g: 0.7843137,
b: 0.7843137,
},
Color {
r: 0.8156863,
g: 0.8156863,
b: 0.3137255,
},
Color {
r: 0.8000000,
g: 0.6274510,
b: 0.3607843,
},
Color {
r: 0.8784314,
g: 0.5803922,
b: 0.4392157,
},
Color {
r: 0.8784314,
g: 0.5333334,
b: 0.5333334,
},
Color {
r: 0.8156863,
g: 0.5176471,
b: 0.7529412,
},
Color {
r: 0.7058824,
g: 0.5176471,
b: 0.8627451,
},
Color {
r: 0.5803922,
g: 0.5333334,
b: 0.8784314,
},
Color {
r: 0.4862745,
g: 0.5490196,
b: 0.8784314,
},
Color {
r: 0.4862745,
g: 0.6117647,
b: 0.8627451,
},
Color {
r: 0.4862745,
g: 0.7058824,
b: 0.8313726,
},
Color {
r: 0.4862745,
g: 0.8156863,
b: 0.6745098,
},
Color {
r: 0.5490196,
g: 0.8156863,
b: 0.5490196,
},
Color {
r: 0.6117647,
g: 0.8000000,
b: 0.4862745,
},
Color {
r: 0.7058824,
g: 0.7529412,
b: 0.4705882,
},
Color {
r: 0.9098039,
g: 0.8000000,
b: 0.4862745,
},
Color {
r: 0.9254902,
g: 0.9254902,
b: 0.9254902,
},
Color {
r: 0.9882353,
g: 0.9882353,
b: 0.4078431,
},
Color {
r: 0.9254902,
g: 0.7843137,
b: 0.4705882,
},
Color {
r: 0.9882353,
g: 0.7372549,
b: 0.5803922,
},
Color {
r: 0.9882353,
g: 0.7058824,
b: 0.7058824,
},
Color {
r: 0.9254902,
g: 0.6901961,
b: 0.8784314,
},
Color {
r: 0.8313726,
g: 0.6901961,
b: 0.9882353,
},
Color {
r: 0.7372549,
g: 0.7058824,
b: 0.9882353,
},
Color {
r: 0.6431373,
g: 0.7215686,
b: 0.9882353,
},
Color {
r: 0.6431373,
g: 0.7843137,
b: 0.9882353,
},
Color {
r: 0.6431373,
g: 0.8784314,
b: 0.9882353,
},
Color {
r: 0.6431373,
g: 0.9882353,
b: 0.8313726,
},
Color {
r: 0.7215686,
g: 0.9882353,
b: 0.7215686,
},
Color {
r: 0.7843137,
g: 0.9882353,
b: 0.6431373,
},
Color {
r: 0.8784314,
g: 0.9254902,
b: 0.6117647,
},
Color {
r: 0.9882353,
g: 0.8784314,
b: 0.5490196,
},
];
Self {
index: 5, colors,
}
}
fn pop_color(&mut self) -> Color {
let color = self.colors.remove(self.index);
self.index = (self.index + 1) % self.colors.len();
color
}
fn push_color(&mut self, color: Color) {
self.colors.push(color)
}
}
fn remove_player(
id: u8,
player_states: &mut HashMap<u8, PlayerState>,
color_picker: &mut ColorPicker,
forced: bool,
) -> bool {
let mut msg = format!(
"Player {} {}",
id,
if forced { "kicked for idling" } else { "left" }
);
if let Some(player_state) = player_states.remove(&id) {
msg.push_str(&format!(
", name: {}, color: {:?}",
player_state.name, player_state.color
));
color_picker.push_color(player_state.color);
println!("{}", msg);
return true;
}
false
}
#[allow(clippy::never_loop)]
fn process_game_control_requests(
game_control_server_socket: &mut zmq::Socket,
game_settings: &mut GameSettings,
player_states: &mut HashMap<u8, PlayerState>,
rng: &mut ThreadRng,
color_picker: &mut ColorPicker,
high_scores: &mut HighScores,
) {
'gamecontrol: loop {
match game_control_server_socket.recv_multipart(0) {
Err(_e) => break 'gamecontrol,
Ok(multipart_message) => {
let return_identity = &multipart_message[0];
let msg: GameControlMsg = deserialize(&multipart_message[2][..]).unwrap();
match msg {
GameControlMsg::Join { name } => {
let result: Result<u8, String>;
loop {
if player_states.len() >= game_settings.max_players as usize {
let err = format!(
"Join Failed: No room for player {} - {} players is the max!",
name, game_settings.max_players
);
println!("{}", err);
result = Err(err);
break;
}
if player_states
.values()
.map(|player_state| &player_state.name)
.any(|x| x == &name)
{
let err =
format!("Join Failed: Name \"{}\" is already taken.", name);
println!("{}", err);
result = Err(err);
break;
}
let mut id;
loop {
id = rng.gen::<u8>();
if (id != 0) && !player_states.contains_key(&id) {
break;
}
}
let color = color_picker.pop_color();
let player_state = PlayerState::new(
&game_settings,
id,
name.clone(),
color,
new_in_square(0.6, rng),
0.05,
);
high_scores.add_player(&player_state.name);
player_states.insert(id, player_state);
println!("Joined: {} (id {})", name, id,);
result = Ok(id);
break;
}
game_control_server_socket
.send_multipart(
&[&return_identity[..], &[], &serialize(&result).unwrap()],
0,
)
.unwrap();
}
GameControlMsg::Leave { id } => {
let succeeded = remove_player(id, player_states, color_picker, false);
if succeeded {
println!("Player {} left voluntarily.", id);
}
game_control_server_socket
.send_multipart(
&[&return_identity[..], &[], &serialize(&succeeded).unwrap()],
0,
)
.unwrap();
}
GameControlMsg::Fetch => {
game_control_server_socket
.send_multipart(
&[
&return_identity[..],
&[],
&serialize(&game_settings).unwrap(),
],
0,
)
.unwrap();
println!("A player fetches new settings.");
}
}
}
}
}
}
fn coalesce_player_input(
player_input_server_socket: &mut zmq::Socket,
player_states: &mut HashMap<u8, PlayerState>,
player_inputs: &mut HashMap<u8, PlayerInput>,
) {
while let Ok(bytes) = player_input_server_socket.recv_bytes(0) {
let player_input: PlayerInput = deserialize(&bytes[..]).unwrap();
if let Some(player_state) = player_states.get_mut(&player_input.id) {
player_state.drop_timer.reset();
}
player_inputs
.entry(player_input.id)
.or_insert_with(|| player_input.clone())
.coalesce(player_input);
}
}
fn update_state(
player_states: &mut HashMap<u8, PlayerState>,
player_inputs: &mut HashMap<u8, PlayerInput>,
game_settings: &mut GameSettings,
color_picker: &mut ColorPicker,
delta: Duration,
rng: &mut ThreadRng,
high_scores: &mut HighScores,
) {
let delta_f32 = delta.f32();
for (id, player_state) in player_states.iter_mut() {
player_state.update(delta);
if player_state.dead && player_state.respawn_timer.ready {
player_state.respawn(new_in_square(0.9, rng), &format!("Player {} spawns", id));
}
}
for (id, player_input) in &mut player_inputs.iter() {
if let Some(player_state) = player_states.get_mut(id) {
if player_state.dead {
player_state.velocity = Vec2::zeros();
continue;
}
player_state.direction = player_input.direction;
let clamped_move_amount = if player_input.move_amount.magnitude() > 1.0 {
player_input.move_amount.normalize()
} else {
player_input.move_amount
};
if clamped_move_amount.magnitude() > game_settings.move_threshold {
player_state.velocity = player_state.velocity
+ (clamped_move_amount * game_settings.acceleration * delta_f32);
} else {
player_state.velocity =
player_state.velocity * (1.0 - (game_settings.drag * delta_f32));
}
if player_input.attack {
clamp_vec_to_magnitude(&mut player_state.velocity, game_settings.max_velocity * 0.5)
} else {
clamp_vec_to_magnitude(&mut player_state.velocity, game_settings.max_velocity);
}
}
}
for (id, player_state) in player_states.iter_mut() {
if player_state.dead {
continue;
}
player_state.pos = player_state.pos + (player_state.velocity * delta_f32);
let boundary = 1.0;
if player_state.pos.x < -boundary
|| player_state.pos.x > boundary
|| player_state.pos.y < -boundary
|| player_state.pos.y > boundary
{
high_scores.penalize(&player_state.name);
player_state.die(&format!(
"Player {} was eaten by a grue. Should have stayed in the light!",
id
));
}
}
let mut attacking_ids: Vec<u8> = vec![];
for (id, player_input) in player_inputs.iter_mut() {
if player_input.attack {
attacking_ids.push(*id);
player_input.attack = false;
}
}
for id in attacking_ids {
let mut attacker: PlayerState;
if let Some(maybe_attacker) = player_states.remove(&id) {
attacker = maybe_attacker;
} else {
continue;
}
if attacker.dead {
player_states.insert(id, attacker);
continue;
}
if !attacker.weapon.attack_timer.ready {
player_states.insert(id, attacker);
continue;
}
attacker.weapon.attack_timer.reset();
let mut missed = true;
for (&defender_id, defender) in player_states.iter_mut() {
if defender.dead {
continue;
}
if distance(&attacker.pos, &defender.pos) <= attacker.weapon.radius + attacker.radius {
missed = false;
if (defender.health > 0.0) && ((defender.health - attacker.weapon.damage) <= 0.0) {
high_scores.score(&attacker.name);
}
defender.health -= attacker.weapon.damage;
attacker
.player_events
.push(PlayerEvent::AttackHit { id: defender_id });
defender.player_events.push(PlayerEvent::TookDamage);
println!(
"Player {} swings and hits ({}) for {:2.1} damage bringing him to {} health.",
id, defender_id, attacker.weapon.damage, defender.health
);
}
}
if missed {
attacker.player_events.push(PlayerEvent::AttackMiss);
}
player_states.insert(id, attacker);
}
let to_process = player_states.drain().collect::<Vec<(u8, PlayerState)>>();
for (id, mut player_state) in to_process {
if player_state.drop_timer.ready {
remove_player(id, player_states, color_picker, true);
continue;
}
if !player_state.dead && player_state.health <= 0.0 {
player_state.die(&format!("Player {} dies from his wounds.", id));
}
player_states.insert(id, player_state);
}
}
fn main() {
let ctx = zmq::Context::new();
let mut game_control_server_socket = ctx.socket(zmq::ROUTER).unwrap();
game_control_server_socket.set_rcvtimeo(0).unwrap();
game_control_server_socket
.bind(&format!("tcp://*:{}", net::GAME_CONTROL_PORT))
.unwrap();
let game_state_server_socket = ctx.socket(zmq::PUB).unwrap();
game_state_server_socket
.bind(&format!("tcp://*:{}", net::GAME_STATE_PORT))
.unwrap();
let mut player_input_server_socket = ctx.socket(zmq::PULL).unwrap();
player_input_server_socket.set_rcvtimeo(0).unwrap();
player_input_server_socket
.bind(&format!("tcp://*:{}", net::PLAYER_INPUT_PORT))
.unwrap();
let mut loop_iterations: i64 = 0;
let mut loop_start = Instant::now();
let mut frame_timer = timer::Timer::from_nanos(16_666_666); let mut color_picker = ColorPicker::new();
let mut game_settings = GameSettings::new();
let mut rng = thread_rng();
let mut frame_number: u64 = 0;
let mut player_states = HashMap::<u8, PlayerState>::new();
let mut player_inputs = HashMap::<u8, PlayerInput>::new();
let mut high_scores = HighScores::new();
let sleep_delay = Duration::from_millis(1);
println!("--------------------------------------------------------------");
println!("Server started (Ctrl-C to stop)\n{:#?}", game_settings);
loop {
let delta = loop_start.elapsed();
loop_start = Instant::now();
frame_timer.update(delta);
if delta < sleep_delay {
thread::sleep(Duration::from_micros(50));
}
loop_iterations += 1;
process_game_control_requests(
&mut game_control_server_socket,
&mut game_settings,
&mut player_states,
&mut rng,
&mut color_picker,
&mut high_scores,
);
coalesce_player_input(
&mut player_input_server_socket,
&mut player_states,
&mut player_inputs,
);
update_state(
&mut player_states,
&mut player_inputs,
&mut game_settings,
&mut color_picker,
delta,
&mut rng,
&mut high_scores,
);
if frame_timer.ready {
frame_timer.reset();
let top10 = high_scores.top10();
if frame_number % 1800 == 0 {
let status = format!(
"STATUS: Frame: {}, Loops during latest frame: {}\n{}",
frame_number, loop_iterations, top10
);
println!("{}", status);
}
let game_state = GameState {
frame_number,
delta,
game_settings_hash: game_settings.get_hash(),
player_states: player_states.clone(),
high_scores: top10,
};
game_state_server_socket
.send(&serialize(&game_state).unwrap(), 0)
.unwrap();
for player_state in player_states.values_mut() {
player_state.new_frame();
}
loop_iterations = 0;
frame_number += 1;
}
}
}