Skip to content

Commit

Permalink
Refactor GameConfig & related (#684)
Browse files Browse the repository at this point in the history
This removes the need to configure max players for the game. It will not
be possible to set the value for multiplayer games with sparse player
numbering (e.g. only player 1, 3, 4 joining).
  • Loading branch information
Indy2222 authored Aug 16, 2023
1 parent d5c34eb commit 73445ea
Show file tree
Hide file tree
Showing 12 changed files with 74 additions and 61 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 6 additions & 8 deletions crates/construction/src/manufacturing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use de_audio::spatial::{PlaySpatialAudioEvent, Sound};
use de_core::{
cleanup::DespawnOnGameExit,
gamestate::GameState,
gconfig::GameConfig,
objects::{Active, ActiveObjectType, ObjectType, UnitType, PLAYER_MAX_UNITS},
player::Player,
projection::{ToAltitude, ToFlat},
Expand Down Expand Up @@ -377,19 +376,18 @@ fn check_spawn_locations(

fn produce(
time: Res<Time>,
conf: Res<GameConfig>,
counter: Res<ObjectCounter>,
mut factories: Query<(Entity, &Player, &mut AssemblyLine)>,
mut deliver_events: EventWriter<DeliverEvent>,
) {
let mut counts: AHashMap<Player, u32> = AHashMap::new();
for player in conf.players() {
let count = counter.player(player).unwrap().unit_count();
counts.insert(player, count);
}
let mut counts: AHashMap<Player, u32> = AHashMap::from_iter(
counter
.counters()
.map(|(&player, counter)| (player, counter.unit_count())),
);

for (factory, &player, mut assembly) in factories.iter_mut() {
let player_count = counts.get_mut(&player).unwrap();
let player_count = counts.entry(player).or_default();

loop {
assembly.blocks_mut().map_capacity = *player_count >= PLAYER_MAX_UNITS;
Expand Down
3 changes: 1 addition & 2 deletions crates/controller/src/commands/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,7 @@ fn place_draft(
mut events: EventWriter<NewDraftEvent>| {
if counter
.player(conf.locals().playable())
.unwrap()
.building_count()
.map_or(0, |c| c.building_count())
>= PLAYER_MAX_BUILDINGS
{
warn!("Maximum number of buildings reached.");
Expand Down
1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ parry3d.workspace = true
paste.workspace = true
serde.workspace = true
thiserror.workspace = true
tinyvec.workspace = true
49 changes: 25 additions & 24 deletions crates/core/src/gconfig.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};

use bevy::prelude::Resource;
use tinyvec::ArrayVec;

use crate::player::{Player, PlayerRange};

Expand All @@ -9,19 +10,13 @@ use crate::player::{Player, PlayerRange};
#[derive(Resource)]
pub struct GameConfig {
map_path: PathBuf,
max_player: Player,
locals: LocalPlayers,
}

impl GameConfig {
pub fn new<P: Into<PathBuf>>(map_path: P, max_player: Player, locals: LocalPlayers) -> Self {
if let Err(err) = locals.validate(max_player) {
panic!("Invalid LocalPlayers configuration: {err:?}");
}

pub fn new<P: Into<PathBuf>>(map_path: P, locals: LocalPlayers) -> Self {
Self {
map_path: map_path.into(),
max_player,
locals,
}
}
Expand All @@ -30,10 +25,6 @@ impl GameConfig {
self.map_path.as_path()
}

pub fn players(&self) -> PlayerRange {
PlayerRange::up_to(self.max_player)
}

pub fn locals(&self) -> &LocalPlayers {
&self.locals
}
Expand All @@ -48,36 +39,47 @@ impl GameConfig {
/// exactly one computer.
pub struct LocalPlayers {
playable: Player,
locals: ArrayVec<[Player; Player::MAX_PLAYERS]>,
}

impl LocalPlayers {
pub fn from_max_player(playable: Player, max_player: Player) -> Self {
Self::from_range(playable, PlayerRange::up_to(max_player))
}

pub fn from_range(playable: Player, locals: PlayerRange) -> Self {
Self::new(playable, locals.collect())
}

/// # Arguments
///
/// * `playable` - the player controlled locally by the user.
pub fn new(playable: Player) -> Self {
Self { playable }
///
/// * `locals` - other players simulated locally on this computer. It must
/// include `playable`.
pub fn new(playable: Player, locals: ArrayVec<[Player; Player::MAX_PLAYERS]>) -> Self {
assert!((*locals).contains(&playable));
Self { playable, locals }
}

/// The player controlled directly by the user on this computer.
pub fn playable(&self) -> Player {
self.playable
}

pub fn locals(&self) -> &[Player] {
self.locals.as_slice()
}

/// Returns true if the player is controlled directly by the user on this
/// computer.
pub fn is_playable(&self, player: Player) -> bool {
self.playable == player
}

fn validate(&self, max_player: Player) -> Result<(), String> {
if self.playable > max_player {
return Err(format!(
"Playable player {} is larger than maximum number of players {max_player}.",
self.playable
));
}

Ok(())
/// Returns true if the player is simulated by this computer.
pub fn is_local(&self, player: Player) -> bool {
self.locals.contains(&player)
}
}

Expand All @@ -89,8 +91,7 @@ mod tests {
fn test_game_config() {
let config = GameConfig::new(
"/some/path",
Player::Player4,
LocalPlayers::new(Player::Player1),
LocalPlayers::from_max_player(Player::Player1, Player::Player4),
);
assert_eq!(config.map_path().to_string_lossy(), "/some/path");
}
Expand Down
5 changes: 4 additions & 1 deletion crates/core/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ use std::cmp::Ordering;
use bevy::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Copy, Clone, Debug, Serialize, Deserialize, Component, PartialEq, Eq, Hash)]
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, Component, PartialEq, Eq, Hash)]
pub enum Player {
#[default]
Player1,
Player2,
Player3,
Player4,
}

impl Player {
pub const MAX_PLAYERS: usize = 4;

pub fn to_num(self) -> u8 {
match self {
Self::Player1 => 1,
Expand Down
4 changes: 2 additions & 2 deletions crates/loader/src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ fn spawn_map(
DespawnOnGameExit,
));

let players = game_config.players();
let locals = game_config.locals();
for object in map.content().objects() {
let (mut entity_commands, object_type) = match object.inner() {
InnerObject::Active(object) => {
let player = object.player();
if !players.contains(player) {
if !locals.is_local(player) {
continue;
}

Expand Down
3 changes: 1 addition & 2 deletions crates/menu/src/singleplayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ fn button_system(
Some(path) => {
commands.insert_resource(GameConfig::new(
path,
Player::Player4,
LocalPlayers::new(Player::Player1),
LocalPlayers::from_max_player(Player::Player1, Player::Player4),
));
next_state.set(AppState::InGame);
}
Expand Down
32 changes: 16 additions & 16 deletions crates/spawner/src/counter.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::ops::{Add, AddAssign};
use std::{
collections::hash_map::Iter,
ops::{Add, AddAssign},
};

use ahash::AHashMap;
use bevy::prelude::*;
use de_core::{
gconfig::GameConfig,
objects::ActiveObjectType,
player::{Player, PlayerRange},
state::AppState,
};
use de_core::{objects::ActiveObjectType, player::Player, state::AppState};

pub(crate) struct CounterPlugin;

Expand All @@ -24,20 +22,22 @@ pub struct ObjectCounter {
}

impl ObjectCounter {
fn new(players: PlayerRange) -> Self {
let mut map = AHashMap::with_capacity(players.len());
for player in players {
map.insert(player, PlayerObjectCounter::default());
fn new() -> Self {
Self {
players: AHashMap::new(),
}
Self { players: map }
}

pub fn counters(&self) -> Iter<Player, PlayerObjectCounter> {
self.players.iter()
}

pub fn player(&self, player: Player) -> Option<&PlayerObjectCounter> {
self.players.get(&player)
}

pub(crate) fn player_mut(&mut self, player: Player) -> Option<&mut PlayerObjectCounter> {
self.players.get_mut(&player)
pub(crate) fn player_mut(&mut self, player: Player) -> &mut PlayerObjectCounter {
self.players.entry(player).or_default()
}
}

Expand Down Expand Up @@ -95,8 +95,8 @@ impl AddAssign<i32> for Count {
}
}

fn setup(mut commands: Commands, config: Res<GameConfig>) {
commands.insert_resource(ObjectCounter::new(config.players()));
fn setup(mut commands: Commands) {
commands.insert_resource(ObjectCounter::new());
}

fn cleanup(mut commands: Commands) {
Expand Down
2 changes: 1 addition & 1 deletion crates/spawner/src/despawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn find_dead(
for (entity, &player, &object_type, health, transform) in entities.iter() {
if health.destroyed() {
if let ObjectType::Active(active_type) = object_type {
counter.player_mut(player).unwrap().update(active_type, -1);
counter.player_mut(player).update(active_type, -1);

play_audio.send(PlaySpatialAudioEvent::new(
match active_type {
Expand Down
19 changes: 15 additions & 4 deletions crates/spawner/src/gameend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,22 @@ fn game_end_detection_system(
counter: Res<ObjectCounter>,
) {
let mut result = None;
if counter.player(conf.locals().playable()).unwrap().total() == 0 {

let (playable, others) =
counter
.counters()
.fold((0, 0), |(playable, others), (&player, counter)| {
let total = counter.total();
if conf.locals().is_playable(player) {
(playable + total, others)
} else {
(playable, others + total)
}
});

if playable == 0 {
result = Some(GameResult::finished(false));
} else if conf.players().all(|player| {
conf.locals().is_playable(player) || counter.player(player).unwrap().total() == 0
}) {
} else if others == 0 {
result = Some(GameResult::finished(true));
}

Expand Down
2 changes: 1 addition & 1 deletion crates/spawner/src/spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fn spawn(
entity_commands.insert(Battery::default());

let player = *player.expect("Active object without an associated was spawned.");
counter.player_mut(player).unwrap().update(active_type, 1);
counter.player_mut(player).update(active_type, 1);

if game_config.locals().is_playable(player) || cfg!(feature = "godmode") {
entity_commands.insert(Playable);
Expand Down

0 comments on commit 73445ea

Please sign in to comment.