mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/choose-starting-site' into 'master'
Allow new players to choose their starting site See merge request veloren/veloren!3848
This commit is contained in:
commit
17cdd3e38d
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
will only lock the camera zoom while movement and combat inputs are also being pressed.
|
will only lock the camera zoom while movement and combat inputs are also being pressed.
|
||||||
- Custom spots can be added without recompilation (only ron and vox files)
|
- Custom spots can be added without recompilation (only ron and vox files)
|
||||||
- Setting in userdata/server/server_config/settings.ron that controls the length of each day/night cycle.
|
- Setting in userdata/server/server_config/settings.ron that controls the length of each day/night cycle.
|
||||||
|
- Starting site can now be chosen during character creation
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Bats move slower and use a simple proportional controller to maintain altitude
|
- Bats move slower and use a simple proportional controller to maintain altitude
|
||||||
|
BIN
assets/voxygen/element/ui/char_select/icons/town_marker.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/ui/char_select/icons/town_marker.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -19,5 +19,10 @@ char_selection-eye_color = Eye Color
|
|||||||
char_selection-skin = Skin
|
char_selection-skin = Skin
|
||||||
char_selection-eyeshape = Eye Details
|
char_selection-eyeshape = Eye Details
|
||||||
char_selection-accessories = Accessories
|
char_selection-accessories = Accessories
|
||||||
|
char_selection-starting_site = Select Starting Area
|
||||||
|
char_selection-starting_site_next = Next
|
||||||
|
char_selection-starting_site_prev = Previous
|
||||||
|
char_selection-starting_site_name = { $name }
|
||||||
|
char_selection-starting_site_kind = Kind: { $kind }
|
||||||
char_selection-create_info_name = Your Character needs a name!
|
char_selection-create_info_name = Your Character needs a name!
|
||||||
char_selection-version_mismatch = WARNING! This server is running a different, possibly incompatible game version. Please update your game.
|
char_selection-version_mismatch = WARNING! This server is running a different, possibly incompatible game version. Please update your game.
|
||||||
|
@ -84,11 +84,11 @@ vec4 fxaa(texture2D tex, sampler smplr, vec2 fragCoord, vec2 resolution,
|
|||||||
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
|
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
|
||||||
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
|
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
|
||||||
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
|
max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
|
||||||
dir * rcpDirMin)) * inverseVP;
|
dir * rcpDirMin)) * inverseVP * 0.75;
|
||||||
|
|
||||||
vec3 rgbA = 0.5 * (
|
vec3 rgbA = 0.5 * (
|
||||||
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
|
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz +
|
||||||
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);
|
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * (1.7 / 3.0 - 0.5)).xyz);
|
||||||
vec3 rgbB = rgbA * 0.5 + 0.25 * (
|
vec3 rgbB = rgbA * 0.5 + 0.25 * (
|
||||||
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * -0.5).xyz +
|
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * -0.5).xyz +
|
||||||
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * 0.5).xyz);
|
texture(sampler2D(tex, smplr), fragCoord * inverseVP + dir * 0.5).xyz);
|
||||||
|
@ -189,6 +189,7 @@ impl BotClient {
|
|||||||
Some("common.items.weapons.sword.starter".to_string()),
|
Some("common.items.weapons.sword.starter".to_string()),
|
||||||
None,
|
None,
|
||||||
body.into(),
|
body.into(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
client.load_character_list();
|
client.load_character_list();
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,7 @@ fn run_client(
|
|||||||
Some("common.items.weapons.sword.starter".into()),
|
Some("common.items.weapons.sword.starter".into()),
|
||||||
None,
|
None,
|
||||||
body(),
|
body(),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
client.load_character_list();
|
client.load_character_list();
|
||||||
|
@ -149,6 +149,8 @@ impl WorldData {
|
|||||||
|
|
||||||
pub fn map_image(&self) -> &Arc<DynamicImage> { &self.map.0[0] }
|
pub fn map_image(&self) -> &Arc<DynamicImage> { &self.map.0[0] }
|
||||||
|
|
||||||
|
pub fn topo_map_image(&self) -> &Arc<DynamicImage> { &self.map.0[1] }
|
||||||
|
|
||||||
pub fn min_chunk_alt(&self) -> f32 { self.map.2.x }
|
pub fn min_chunk_alt(&self) -> f32 { self.map.2.x }
|
||||||
|
|
||||||
pub fn max_chunk_alt(&self) -> f32 { self.map.2.y }
|
pub fn max_chunk_alt(&self) -> f32 { self.map.2.y }
|
||||||
@ -953,6 +955,7 @@ impl Client {
|
|||||||
mainhand: Option<String>,
|
mainhand: Option<String>,
|
||||||
offhand: Option<String>,
|
offhand: Option<String>,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
|
start_site: Option<SiteId>,
|
||||||
) {
|
) {
|
||||||
self.character_list.loading = true;
|
self.character_list.loading = true;
|
||||||
self.send_msg(ClientGeneral::CreateCharacter {
|
self.send_msg(ClientGeneral::CreateCharacter {
|
||||||
@ -960,6 +963,7 @@ impl Client {
|
|||||||
mainhand,
|
mainhand,
|
||||||
offhand,
|
offhand,
|
||||||
body,
|
body,
|
||||||
|
start_site,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ pub enum ClientGeneral {
|
|||||||
mainhand: Option<String>,
|
mainhand: Option<String>,
|
||||||
offhand: Option<String>,
|
offhand: Option<String>,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
|
start_site: Option<SiteId>,
|
||||||
},
|
},
|
||||||
DeleteCharacter(CharacterId),
|
DeleteCharacter(CharacterId),
|
||||||
EditCharacter {
|
EditCharacter {
|
||||||
|
@ -272,7 +272,6 @@ pub enum ServerChatCommand {
|
|||||||
GroupPromote,
|
GroupPromote,
|
||||||
Health,
|
Health,
|
||||||
Help,
|
Help,
|
||||||
Home,
|
|
||||||
JoinFaction,
|
JoinFaction,
|
||||||
Jump,
|
Jump,
|
||||||
Kick,
|
Kick,
|
||||||
@ -294,6 +293,7 @@ pub enum ServerChatCommand {
|
|||||||
Region,
|
Region,
|
||||||
ReloadChunks,
|
ReloadChunks,
|
||||||
RemoveLights,
|
RemoveLights,
|
||||||
|
Respawn,
|
||||||
RevokeBuild,
|
RevokeBuild,
|
||||||
RevokeBuildAll,
|
RevokeBuildAll,
|
||||||
Safezone,
|
Safezone,
|
||||||
@ -487,7 +487,7 @@ impl ServerChatCommand {
|
|||||||
"Display information about commands",
|
"Display information about commands",
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
ServerChatCommand::Home => cmd(vec![], "Return to the home town", Some(Moderator)),
|
ServerChatCommand::Respawn => cmd(vec![], "Teleport to your waypoint", Some(Moderator)),
|
||||||
ServerChatCommand::JoinFaction => ChatCommandData::new(
|
ServerChatCommand::JoinFaction => ChatCommandData::new(
|
||||||
vec![Any("faction", Optional)],
|
vec![Any("faction", Optional)],
|
||||||
"Join/leave the specified faction",
|
"Join/leave the specified faction",
|
||||||
@ -762,7 +762,7 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::GroupLeave => "group_leave",
|
ServerChatCommand::GroupLeave => "group_leave",
|
||||||
ServerChatCommand::Health => "health",
|
ServerChatCommand::Health => "health",
|
||||||
ServerChatCommand::Help => "help",
|
ServerChatCommand::Help => "help",
|
||||||
ServerChatCommand::Home => "home",
|
ServerChatCommand::Respawn => "respawn",
|
||||||
ServerChatCommand::JoinFaction => "join_faction",
|
ServerChatCommand::JoinFaction => "join_faction",
|
||||||
ServerChatCommand::Jump => "jump",
|
ServerChatCommand::Jump => "jump",
|
||||||
ServerChatCommand::Kick => "kick",
|
ServerChatCommand::Kick => "kick",
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use crate::persistence::{character_updater::CharacterUpdater, PersistedComponents};
|
use crate::persistence::{character_updater::CharacterUpdater, PersistedComponents};
|
||||||
use common::{
|
use common::{
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
comp::{inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, SkillSet, Stats},
|
comp::{
|
||||||
|
inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, SkillSet, Stats,
|
||||||
|
Waypoint,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use specs::{Entity, WriteExpect};
|
use specs::{Entity, WriteExpect};
|
||||||
|
|
||||||
@ -32,6 +35,7 @@ pub fn create_character(
|
|||||||
character_offhand: Option<String>,
|
character_offhand: Option<String>,
|
||||||
body: Body,
|
body: Body,
|
||||||
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
|
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
|
||||||
|
waypoint: Option<Waypoint>,
|
||||||
) -> Result<(), CreationError> {
|
) -> Result<(), CreationError> {
|
||||||
// quick fix whitelist validation for now; eventually replace the
|
// quick fix whitelist validation for now; eventually replace the
|
||||||
// `Option<String>` with an index into a server-provided list of starter
|
// `Option<String>` with an index into a server-provided list of starter
|
||||||
@ -63,7 +67,6 @@ pub fn create_character(
|
|||||||
.push(Item::new_from_asset_expect("common.items.food.cheese"))
|
.push(Item::new_from_asset_expect("common.items.food.cheese"))
|
||||||
.expect("Inventory has at least 1 slot left!");
|
.expect("Inventory has at least 1 slot left!");
|
||||||
|
|
||||||
let waypoint = None;
|
|
||||||
let map_marker = None;
|
let map_marker = None;
|
||||||
|
|
||||||
character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents {
|
character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents {
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
weather::WeatherSim,
|
weather::WeatherSim,
|
||||||
wiring,
|
wiring,
|
||||||
wiring::OutputFormula,
|
wiring::OutputFormula,
|
||||||
Server, Settings, SpawnPoint, StateExt,
|
Server, Settings, StateExt,
|
||||||
};
|
};
|
||||||
use assets::AssetExt;
|
use assets::AssetExt;
|
||||||
use authc::Uuid;
|
use authc::Uuid;
|
||||||
@ -150,7 +150,7 @@ fn do_command(
|
|||||||
ServerChatCommand::GroupPromote => handle_group_promote,
|
ServerChatCommand::GroupPromote => handle_group_promote,
|
||||||
ServerChatCommand::Health => handle_health,
|
ServerChatCommand::Health => handle_health,
|
||||||
ServerChatCommand::Help => handle_help,
|
ServerChatCommand::Help => handle_help,
|
||||||
ServerChatCommand::Home => handle_home,
|
ServerChatCommand::Respawn => handle_respawn,
|
||||||
ServerChatCommand::JoinFaction => handle_join_faction,
|
ServerChatCommand::JoinFaction => handle_join_faction,
|
||||||
ServerChatCommand::Jump => handle_jump,
|
ServerChatCommand::Jump => handle_jump,
|
||||||
ServerChatCommand::Kick => handle_kick,
|
ServerChatCommand::Kick => handle_kick,
|
||||||
@ -874,25 +874,23 @@ fn handle_site(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_home(
|
fn handle_respawn(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
_client: EcsEntity,
|
_client: EcsEntity,
|
||||||
target: EcsEntity,
|
target: EcsEntity,
|
||||||
_args: Vec<String>,
|
_args: Vec<String>,
|
||||||
_action: &ServerChatCommand,
|
_action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
let home_pos = server.state.mut_resource::<SpawnPoint>().0;
|
let waypoint = server
|
||||||
let time = *server.state.mut_resource::<Time>();
|
.state
|
||||||
|
.read_storage::<comp::Waypoint>()
|
||||||
|
.get(target)
|
||||||
|
.ok_or("No waypoint set")?
|
||||||
|
.get_pos();
|
||||||
|
|
||||||
position_mut(server, target, "target", |current_pos| {
|
position_mut(server, target, "target", |current_pos| {
|
||||||
current_pos.0 = home_pos
|
current_pos.0 = waypoint;
|
||||||
})?;
|
})
|
||||||
insert_or_replace_component(
|
|
||||||
server,
|
|
||||||
target,
|
|
||||||
comp::Waypoint::temp_new(home_pos, time),
|
|
||||||
"target",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_kill(
|
fn handle_kill(
|
||||||
|
@ -135,6 +135,9 @@ use world::{
|
|||||||
IndexOwned, World,
|
IndexOwned, World,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// SpawnPoint corresponds to the default location that players are positioned
|
||||||
|
/// at if they have no waypoint. Players *should* always have a waypoint, so
|
||||||
|
/// this should basically never be used in practice.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct SpawnPoint(pub Vec3<f32>);
|
pub struct SpawnPoint(pub Vec3<f32>);
|
||||||
|
|
||||||
@ -395,6 +398,8 @@ impl Server {
|
|||||||
|
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let spawn_point = SpawnPoint({
|
let spawn_point = SpawnPoint({
|
||||||
|
use world::civ::SiteKind;
|
||||||
|
|
||||||
let index = index.as_index_ref();
|
let index = index.as_index_ref();
|
||||||
// NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops,
|
// NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops,
|
||||||
// but are needed to be explicit about casting (and to make the compiler stop
|
// but are needed to be explicit about casting (and to make the compiler stop
|
||||||
@ -402,27 +407,14 @@ impl Server {
|
|||||||
|
|
||||||
// Search for town defined by spawn_town server setting. If this fails, or is
|
// Search for town defined by spawn_town server setting. If this fails, or is
|
||||||
// None, set spawn to the nearest town to the centre of the world
|
// None, set spawn to the nearest town to the centre of the world
|
||||||
let spawn_chunk = match settings.spawn_town.as_ref().and_then(|spawn_town| {
|
let center_chunk = world.sim().map_size_lg().chunks().map(i32::from) / 2;
|
||||||
world.civs().sites().find(|site| {
|
let spawn_chunk = world
|
||||||
site.site_tmp
|
.civs()
|
||||||
.map_or(false, |id| index.sites[id].name() == spawn_town)
|
.sites()
|
||||||
})
|
.filter(|site| matches!(site.kind, SiteKind::Settlement | SiteKind::Refactor))
|
||||||
}) {
|
.map(|site| site.center)
|
||||||
Some(t) => t.center,
|
.min_by_key(|site_pos| site_pos.distance_squared(center_chunk))
|
||||||
None => {
|
.unwrap_or(center_chunk);
|
||||||
let center_chunk = world.sim().map_size_lg().chunks().map(i32::from) / 2;
|
|
||||||
use world::civ::SiteKind;
|
|
||||||
world
|
|
||||||
.civs()
|
|
||||||
.sites()
|
|
||||||
.filter(|site| {
|
|
||||||
matches!(site.kind, SiteKind::Settlement | SiteKind::Refactor)
|
|
||||||
})
|
|
||||||
.map(|site| site.center)
|
|
||||||
.min_by_key(|site_pos| site_pos.distance_squared(center_chunk))
|
|
||||||
.unwrap_or(center_chunk)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
world.find_accessible_pos(index, TerrainChunkSize::center_wpos(spawn_chunk), false)
|
world.find_accessible_pos(index, TerrainChunkSize::center_wpos(spawn_chunk), false)
|
||||||
});
|
});
|
||||||
@ -558,7 +550,7 @@ impl Server {
|
|||||||
// Initiate real-time world simulation
|
// Initiate real-time world simulation
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
{
|
{
|
||||||
rtsim::init(&mut state, &world, index.as_index_ref(), spawn_point);
|
rtsim::init(&mut state, &world, index.as_index_ref());
|
||||||
weather::init(&mut state, &world);
|
weather::init(&mut state, &world);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
|
@ -110,7 +110,6 @@ pub fn init(
|
|||||||
state: &mut State,
|
state: &mut State,
|
||||||
#[cfg(feature = "worldgen")] world: &world::World,
|
#[cfg(feature = "worldgen")] world: &world::World,
|
||||||
#[cfg(feature = "worldgen")] index: world::IndexRef,
|
#[cfg(feature = "worldgen")] index: world::IndexRef,
|
||||||
#[cfg(feature = "worldgen")] spawn_point: crate::SpawnPoint,
|
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let mut rtsim = RtSim::new(world.sim().get_size());
|
let mut rtsim = RtSim::new(world.sim().get_size());
|
||||||
@ -153,21 +152,6 @@ pub fn init(
|
|||||||
.filter_map(|(site_id, site)| site.site_tmp.map(|id| (site_id, &index.sites[id])))
|
.filter_map(|(site_id, site)| site.site_tmp.map(|id| (site_id, &index.sites[id])))
|
||||||
{
|
{
|
||||||
use world::site::SiteKind;
|
use world::site::SiteKind;
|
||||||
let spawn_town_id = world
|
|
||||||
.civs()
|
|
||||||
.sites
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, site)| site.is_settlement())
|
|
||||||
.min_by_key(|(_, site)| {
|
|
||||||
let wpos = site
|
|
||||||
.center
|
|
||||||
.as_::<i64>()
|
|
||||||
.map2(TerrainChunk::RECT_SIZE.as_::<i64>(), |e, sz| {
|
|
||||||
e * sz + sz / 2
|
|
||||||
});
|
|
||||||
wpos.distance_squared(spawn_point.0.xy().map(|x| x as i64))
|
|
||||||
})
|
|
||||||
.map(|(id, _)| id);
|
|
||||||
match &site.kind {
|
match &site.kind {
|
||||||
#[allow(clippy::single_match)]
|
#[allow(clippy::single_match)]
|
||||||
SiteKind::Dungeon(dungeon) => match dungeon.dungeon_difficulty() {
|
SiteKind::Dungeon(dungeon) => match dungeon.dungeon_difficulty() {
|
||||||
@ -177,12 +161,7 @@ pub fn init(
|
|||||||
.civs()
|
.civs()
|
||||||
.sites
|
.sites
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(site_id, site)| {
|
.filter(|(_, site)| site.is_settlement())
|
||||||
site.is_settlement()
|
|
||||||
// TODO: Remove this later, starting town should not be
|
|
||||||
// special-cased
|
|
||||||
&& spawn_town_id.map_or(false, |spawn_id| spawn_id != site_id)
|
|
||||||
})
|
|
||||||
.min_by_key(|(_, site)| {
|
.min_by_key(|(_, site)| {
|
||||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||||
wpos.map(|e| e as f32)
|
wpos.map(|e| e as f32)
|
||||||
|
@ -81,8 +81,6 @@ pub struct GameplaySettings {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub battle_mode: ServerBattleMode,
|
pub battle_mode: ServerBattleMode,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub safe_spawn: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub explosion_burn_marks: bool,
|
pub explosion_burn_marks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +88,6 @@ impl Default for GameplaySettings {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
battle_mode: ServerBattleMode::default(),
|
battle_mode: ServerBattleMode::default(),
|
||||||
safe_spawn: false,
|
|
||||||
explosion_burn_marks: true,
|
explosion_burn_marks: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +172,6 @@ pub struct Settings {
|
|||||||
pub max_view_distance: Option<u32>,
|
pub max_view_distance: Option<u32>,
|
||||||
pub max_player_group_size: u32,
|
pub max_player_group_size: u32,
|
||||||
pub client_timeout: Duration,
|
pub client_timeout: Duration,
|
||||||
pub spawn_town: Option<String>,
|
|
||||||
pub max_player_for_kill_broadcast: Option<usize>,
|
pub max_player_for_kill_broadcast: Option<usize>,
|
||||||
pub calendar_mode: CalendarMode,
|
pub calendar_mode: CalendarMode,
|
||||||
|
|
||||||
@ -213,7 +209,6 @@ impl Default for Settings {
|
|||||||
max_player_group_size: 6,
|
max_player_group_size: 6,
|
||||||
calendar_mode: CalendarMode::Auto,
|
calendar_mode: CalendarMode::Auto,
|
||||||
client_timeout: Duration::from_secs(40),
|
client_timeout: Duration::from_secs(40),
|
||||||
spawn_town: None,
|
|
||||||
max_player_for_kill_broadcast: None,
|
max_player_for_kill_broadcast: None,
|
||||||
experimental_terrain_persistence: false,
|
experimental_terrain_persistence: false,
|
||||||
gameplay: GameplaySettings::default(),
|
gameplay: GameplaySettings::default(),
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
#[cfg(not(feature = "worldgen"))]
|
||||||
|
use crate::test_world::{IndexOwned, World};
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
|
use world::{IndexOwned, World};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
automod::AutoMod,
|
automod::AutoMod,
|
||||||
character_creator,
|
character_creator,
|
||||||
@ -7,17 +12,20 @@ use crate::{
|
|||||||
EditableSettings,
|
EditableSettings,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg},
|
comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg, Waypoint},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
|
resources::Time,
|
||||||
|
terrain::TerrainChunkSize,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::{ClientGeneral, ServerGeneral};
|
use common_net::msg::{ClientGeneral, ServerGeneral};
|
||||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
|
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
|
||||||
use std::sync::{atomic::Ordering, Arc};
|
use std::sync::{atomic::Ordering, Arc};
|
||||||
use tracing::debug;
|
use tracing::{debug, error};
|
||||||
|
|
||||||
impl Sys {
|
impl Sys {
|
||||||
|
#[allow(clippy::too_many_arguments)] // Shhhh, go bother someone else clippy
|
||||||
fn handle_client_character_screen_msg(
|
fn handle_client_character_screen_msg(
|
||||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||||
entity: specs::Entity,
|
entity: specs::Entity,
|
||||||
@ -32,6 +40,9 @@ impl Sys {
|
|||||||
censor: &ReadExpect<'_, Arc<censor::Censor>>,
|
censor: &ReadExpect<'_, Arc<censor::Censor>>,
|
||||||
automod: &AutoMod,
|
automod: &AutoMod,
|
||||||
msg: ClientGeneral,
|
msg: ClientGeneral,
|
||||||
|
time: Time,
|
||||||
|
index: &ReadExpect<'_, IndexOwned>,
|
||||||
|
world: &ReadExpect<'_, Arc<World>>,
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
let mut send_join_messages = || -> Result<(), crate::error::Error> {
|
let mut send_join_messages = || -> Result<(), crate::error::Error> {
|
||||||
// Give the player a welcome message
|
// Give the player a welcome message
|
||||||
@ -135,6 +146,7 @@ impl Sys {
|
|||||||
mainhand,
|
mainhand,
|
||||||
offhand,
|
offhand,
|
||||||
body,
|
body,
|
||||||
|
start_site,
|
||||||
} => {
|
} => {
|
||||||
if censor.check(&alias) {
|
if censor.check(&alias) {
|
||||||
debug!(?alias, "denied alias as it contained a banned word");
|
debug!(?alias, "denied alias as it contained a banned word");
|
||||||
@ -151,6 +163,22 @@ impl Sys {
|
|||||||
offhand.clone(),
|
offhand.clone(),
|
||||||
body,
|
body,
|
||||||
character_updater,
|
character_updater,
|
||||||
|
start_site.and_then(|site_idx| {
|
||||||
|
// TODO: This corresponds to the ID generation logic in `world/src/lib.rs`
|
||||||
|
// Really, we should have a way to consistently refer to sites, but that's a job for rtsim2
|
||||||
|
// and the site changes that it will require. Until then, this code is very hacky.
|
||||||
|
world.civs().sites.iter()
|
||||||
|
.find(|(_, site)| site.site_tmp.map(|i| i.id()) == Some(site_idx))
|
||||||
|
.map(Some)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
error!("Tried to create character with starting site index {}, but such a site does not exist", site_idx);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.map(|(_, site)| {
|
||||||
|
let wpos2d = TerrainChunkSize::center_wpos(site.center);
|
||||||
|
Waypoint::new(world.find_accessible_pos(index.as_index_ref(), wpos2d, false), time)
|
||||||
|
})
|
||||||
|
}),
|
||||||
) {
|
) {
|
||||||
debug!(
|
debug!(
|
||||||
?error,
|
?error,
|
||||||
@ -226,6 +254,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadExpect<'a, EditableSettings>,
|
ReadExpect<'a, EditableSettings>,
|
||||||
ReadExpect<'a, Arc<censor::Censor>>,
|
ReadExpect<'a, Arc<censor::Censor>>,
|
||||||
ReadExpect<'a, AutoMod>,
|
ReadExpect<'a, AutoMod>,
|
||||||
|
ReadExpect<'a, Time>,
|
||||||
|
ReadExpect<'a, IndexOwned>,
|
||||||
|
ReadExpect<'a, Arc<World>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const NAME: &'static str = "msg::character_screen";
|
const NAME: &'static str = "msg::character_screen";
|
||||||
@ -247,6 +278,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
editable_settings,
|
editable_settings,
|
||||||
censor,
|
censor,
|
||||||
automod,
|
automod,
|
||||||
|
time,
|
||||||
|
index,
|
||||||
|
world,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
let mut server_emitter = server_event_bus.emitter();
|
let mut server_emitter = server_event_bus.emitter();
|
||||||
@ -267,6 +301,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
&censor,
|
&censor,
|
||||||
&automod,
|
&automod,
|
||||||
msg,
|
msg,
|
||||||
|
*time,
|
||||||
|
&index,
|
||||||
|
&world,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use crate::{
|
|||||||
presence::{Presence, RepositionOnChunkLoad},
|
presence::{Presence, RepositionOnChunkLoad},
|
||||||
rtsim::RtSim,
|
rtsim::RtSim,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
ChunkRequest, SpawnPoint, Tick,
|
ChunkRequest, Tick,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
@ -62,7 +62,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
type SystemData = (
|
type SystemData = (
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
Read<'a, Tick>,
|
Read<'a, Tick>,
|
||||||
Read<'a, SpawnPoint>,
|
|
||||||
Read<'a, Settings>,
|
Read<'a, Settings>,
|
||||||
Read<'a, TimeOfDay>,
|
Read<'a, TimeOfDay>,
|
||||||
Read<'a, Calendar>,
|
Read<'a, Calendar>,
|
||||||
@ -95,7 +94,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
(
|
(
|
||||||
server_event_bus,
|
server_event_bus,
|
||||||
tick,
|
tick,
|
||||||
spawn_point,
|
|
||||||
server_settings,
|
server_settings,
|
||||||
time_of_day,
|
time_of_day,
|
||||||
calendar,
|
calendar,
|
||||||
@ -227,14 +225,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a safezone if chunk contains the spawn position
|
|
||||||
if server_settings.gameplay.safe_spawn && is_spawn_chunk(key, *spawn_point) {
|
|
||||||
server_emitter.emit(ServerEvent::CreateSafezone {
|
|
||||||
range: Some(SAFE_ZONE_RADIUS),
|
|
||||||
pos: Pos(spawn_point.0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider putting this in another system since this forces us to take
|
// TODO: Consider putting this in another system since this forces us to take
|
||||||
@ -711,9 +701,3 @@ pub fn chunk_in_vd(player_chunk_pos: Vec2<i16>, player_vd_sqr: i32, chunk_pos: V
|
|||||||
|
|
||||||
adjusted_dist_sqr <= player_vd_sqr
|
adjusted_dist_sqr <= player_vd_sqr
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_spawn_chunk(chunk_pos: Vec2<i32>, spawn_pos: SpawnPoint) -> bool {
|
|
||||||
// FIXME: Ensure spawn_pos doesn't overflow before performing this cast.
|
|
||||||
let spawn_chunk_pos = TerrainGrid::chunk_key(spawn_pos.0.map(|e| e as i32));
|
|
||||||
chunk_pos == spawn_chunk_pos
|
|
||||||
}
|
|
||||||
|
@ -796,8 +796,7 @@ impl<'a> Widget for Map<'a> {
|
|||||||
.graphics_for(state.ids.show_peaks_box)
|
.graphics_for(state.ids.show_peaks_box)
|
||||||
.color(TEXT_COLOR)
|
.color(TEXT_COLOR)
|
||||||
.set(state.ids.show_peaks_text, ui);
|
.set(state.ids.show_peaks_text, ui);
|
||||||
// Voxel map (TODO: enable this once Pfau approves the final UI, and once
|
|
||||||
// there's a non-placeholder graphic for the checkbox)
|
|
||||||
const EXPOSE_VOXEL_MAP_TOGGLE_IN_UI: bool = false;
|
const EXPOSE_VOXEL_MAP_TOGGLE_IN_UI: bool = false;
|
||||||
if EXPOSE_VOXEL_MAP_TOGGLE_IN_UI {
|
if EXPOSE_VOXEL_MAP_TOGGLE_IN_UI {
|
||||||
Image::new(self.imgs.mmap_poi_peak)
|
Image::new(self.imgs.mmap_poi_peak)
|
||||||
|
@ -222,6 +222,9 @@ const SPEECH_BUBBLE_RANGE: f32 = NAMETAG_RANGE;
|
|||||||
const EXP_FLOATER_LIFETIME: f32 = 2.0;
|
const EXP_FLOATER_LIFETIME: f32 = 2.0;
|
||||||
const EXP_ACCUMULATION_DURATION: f32 = 0.5;
|
const EXP_ACCUMULATION_DURATION: f32 = 0.5;
|
||||||
|
|
||||||
|
// TODO: Don't hard code this
|
||||||
|
pub fn default_water_color() -> Rgba<f32> { srgba_to_linear(Rgba::new(0.0, 0.18, 0.37, 1.0)) }
|
||||||
|
|
||||||
widget_ids! {
|
widget_ids! {
|
||||||
struct Ids {
|
struct Ids {
|
||||||
// Crosshair
|
// Crosshair
|
||||||
@ -1303,15 +1306,15 @@ impl Hud {
|
|||||||
ui.set_scaling_mode(settings.interface.ui_scale);
|
ui.set_scaling_mode(settings.interface.ui_scale);
|
||||||
// Generate ids.
|
// Generate ids.
|
||||||
let ids = Ids::new(ui.id_generator());
|
let ids = Ids::new(ui.id_generator());
|
||||||
// NOTE: Use a border the same color as the LOD ocean color (but with a
|
|
||||||
// translucent alpha since UI have transparency and LOD doesn't).
|
|
||||||
let water_color = srgba_to_linear(Rgba::new(0.0, 0.18, 0.37, 1.0));
|
|
||||||
// Load world map
|
// Load world map
|
||||||
let mut layers = Vec::new();
|
let mut layers = Vec::new();
|
||||||
for layer in client.world_data().map_layers() {
|
for layer in client.world_data().map_layers() {
|
||||||
layers.push(
|
// NOTE: Use a border the same color as the LOD ocean color (but with a
|
||||||
ui.add_graphic_with_rotations(Graphic::Image(Arc::clone(layer), Some(water_color))),
|
// translucent alpha since UI have transparency and LOD doesn't).
|
||||||
);
|
layers.push(ui.add_graphic_with_rotations(Graphic::Image(
|
||||||
|
Arc::clone(layer),
|
||||||
|
Some(default_water_color()),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
let world_map = (layers, client.world_data().chunk_size().map(|e| e as u32));
|
let world_map = (layers, client.world_data().chunk_size().map(|e| e as u32));
|
||||||
// Load images.
|
// Load images.
|
||||||
|
@ -117,10 +117,11 @@ impl PlayState for CharSelectionState {
|
|||||||
mainhand,
|
mainhand,
|
||||||
offhand,
|
offhand,
|
||||||
body,
|
body,
|
||||||
|
start_site,
|
||||||
} => {
|
} => {
|
||||||
self.client
|
self.client
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.create_character(alias, mainhand, offhand, body);
|
.create_character(alias, mainhand, offhand, body, start_site);
|
||||||
},
|
},
|
||||||
ui::Event::EditCharacter {
|
ui::Event::EditCharacter {
|
||||||
alias,
|
alias,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
hud::default_water_color,
|
||||||
render::UiDrawer,
|
render::UiDrawer,
|
||||||
ui::{
|
ui::{
|
||||||
self,
|
self,
|
||||||
@ -16,6 +17,7 @@ use crate::{
|
|||||||
Element, IcedRenderer, IcedUi as Ui,
|
Element, IcedRenderer, IcedUi as Ui,
|
||||||
},
|
},
|
||||||
img_ids::ImageGraphic,
|
img_ids::ImageGraphic,
|
||||||
|
Graphic, GraphicId,
|
||||||
},
|
},
|
||||||
window, GlobalState,
|
window, GlobalState,
|
||||||
};
|
};
|
||||||
@ -23,18 +25,22 @@ use client::{Client, ServerInfo};
|
|||||||
use common::{
|
use common::{
|
||||||
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER, MAX_NAME_LENGTH},
|
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER, MAX_NAME_LENGTH},
|
||||||
comp::{self, humanoid, inventory::slot::EquipSlot, Inventory, Item},
|
comp::{self, humanoid, inventory::slot::EquipSlot, Inventory, Item},
|
||||||
|
terrain::TerrainChunkSize,
|
||||||
|
vol::RectVolSize,
|
||||||
LoadoutBuilder,
|
LoadoutBuilder,
|
||||||
};
|
};
|
||||||
|
use common_net::msg::world_msg::{SiteId, SiteInfo, SiteKind};
|
||||||
use i18n::{Localization, LocalizationHandle};
|
use i18n::{Localization, LocalizationHandle};
|
||||||
//ImageFrame, Tooltip,
|
//ImageFrame, Tooltip,
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
//use std::time::Duration;
|
//use std::time::Duration;
|
||||||
//use ui::ice::widget;
|
//use ui::ice::widget;
|
||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, slider, text_input, Align, Button, Column, Container, HorizontalAlignment,
|
button, scrollable, slider, text_input, Align, Button, Color, Column, Container,
|
||||||
Length, Row, Scrollable, Slider, Space, Text, TextInput,
|
HorizontalAlignment, Length, Row, Scrollable, Slider, Space, Text, TextInput,
|
||||||
};
|
};
|
||||||
use vek::Rgba;
|
use std::sync::Arc;
|
||||||
|
use vek::{Rgba, Vec2};
|
||||||
|
|
||||||
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
|
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
|
||||||
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
|
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
|
||||||
@ -121,6 +127,9 @@ image_ids_ice! {
|
|||||||
// Tooltips
|
// Tooltips
|
||||||
tt_edge: "voxygen.element.ui.generic.frames.tooltip.edge",
|
tt_edge: "voxygen.element.ui.generic.frames.tooltip.edge",
|
||||||
tt_corner: "voxygen.element.ui.generic.frames.tooltip.corner",
|
tt_corner: "voxygen.element.ui.generic.frames.tooltip.corner",
|
||||||
|
|
||||||
|
// Startzone Selection
|
||||||
|
town_marker: "voxygen.element.ui.char_select.icons.town_marker",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +142,7 @@ pub enum Event {
|
|||||||
mainhand: Option<String>,
|
mainhand: Option<String>,
|
||||||
offhand: Option<String>,
|
offhand: Option<String>,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
|
start_site: Option<SiteId>,
|
||||||
},
|
},
|
||||||
EditCharacter {
|
EditCharacter {
|
||||||
alias: String,
|
alias: String,
|
||||||
@ -168,13 +178,20 @@ enum Mode {
|
|||||||
species_buttons: [button::State; 6],
|
species_buttons: [button::State; 6],
|
||||||
tool_buttons: [button::State; 6],
|
tool_buttons: [button::State; 6],
|
||||||
sliders: Sliders,
|
sliders: Sliders,
|
||||||
scroll: scrollable::State,
|
left_scroll: scrollable::State,
|
||||||
|
right_scroll: scrollable::State,
|
||||||
name_input: text_input::State,
|
name_input: text_input::State,
|
||||||
back_button: button::State,
|
back_button: button::State,
|
||||||
create_button: button::State,
|
create_button: button::State,
|
||||||
rand_character_button: button::State,
|
rand_character_button: button::State,
|
||||||
rand_name_button: button::State,
|
rand_name_button: button::State,
|
||||||
|
prev_starting_site_button: button::State,
|
||||||
|
next_starting_site_button: button::State,
|
||||||
|
/// `character_id.is_some()` can be used to determine if we're in edit
|
||||||
|
/// mode as opposed to create mode.
|
||||||
|
// TODO: Something less janky? Express the problem domain better!
|
||||||
character_id: Option<CharacterId>,
|
character_id: Option<CharacterId>,
|
||||||
|
start_site_idx: usize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,13 +234,17 @@ impl Mode {
|
|||||||
species_buttons: Default::default(),
|
species_buttons: Default::default(),
|
||||||
tool_buttons: Default::default(),
|
tool_buttons: Default::default(),
|
||||||
sliders: Default::default(),
|
sliders: Default::default(),
|
||||||
scroll: Default::default(),
|
left_scroll: Default::default(),
|
||||||
|
right_scroll: Default::default(),
|
||||||
name_input: Default::default(),
|
name_input: Default::default(),
|
||||||
back_button: Default::default(),
|
back_button: Default::default(),
|
||||||
create_button: Default::default(),
|
create_button: Default::default(),
|
||||||
rand_character_button: Default::default(),
|
rand_character_button: Default::default(),
|
||||||
rand_name_button: Default::default(),
|
rand_name_button: Default::default(),
|
||||||
|
prev_starting_site_button: Default::default(),
|
||||||
|
next_starting_site_button: Default::default(),
|
||||||
character_id: None,
|
character_id: None,
|
||||||
|
start_site_idx: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,13 +264,17 @@ impl Mode {
|
|||||||
species_buttons: Default::default(),
|
species_buttons: Default::default(),
|
||||||
tool_buttons: Default::default(),
|
tool_buttons: Default::default(),
|
||||||
sliders: Default::default(),
|
sliders: Default::default(),
|
||||||
scroll: Default::default(),
|
left_scroll: Default::default(),
|
||||||
|
right_scroll: Default::default(),
|
||||||
name_input: Default::default(),
|
name_input: Default::default(),
|
||||||
back_button: Default::default(),
|
back_button: Default::default(),
|
||||||
create_button: Default::default(),
|
create_button: Default::default(),
|
||||||
rand_character_button: Default::default(),
|
rand_character_button: Default::default(),
|
||||||
rand_name_button: Default::default(),
|
rand_name_button: Default::default(),
|
||||||
|
prev_starting_site_button: Default::default(),
|
||||||
|
next_starting_site_button: Default::default(),
|
||||||
character_id: Some(character_id),
|
character_id: Some(character_id),
|
||||||
|
start_site_idx: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,6 +304,9 @@ struct Controls {
|
|||||||
// Id of the selected character
|
// Id of the selected character
|
||||||
selected: Option<CharacterId>,
|
selected: Option<CharacterId>,
|
||||||
default_name: String,
|
default_name: String,
|
||||||
|
map_img: GraphicId,
|
||||||
|
possible_starting_sites: Vec<SiteInfo>,
|
||||||
|
world_sz: Vec2<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -309,6 +337,9 @@ enum Message {
|
|||||||
EyeColor(u8),
|
EyeColor(u8),
|
||||||
Accessory(u8),
|
Accessory(u8),
|
||||||
Beard(u8),
|
Beard(u8),
|
||||||
|
StartingSite(usize),
|
||||||
|
PrevStartingSite,
|
||||||
|
NextStartingSite,
|
||||||
// Workaround for widgets that require a message but we don't want them to actually do
|
// Workaround for widgets that require a message but we don't want them to actually do
|
||||||
// anything
|
// anything
|
||||||
DoNothing,
|
DoNothing,
|
||||||
@ -321,6 +352,9 @@ impl Controls {
|
|||||||
selected: Option<CharacterId>,
|
selected: Option<CharacterId>,
|
||||||
default_name: String,
|
default_name: String,
|
||||||
server_info: &ServerInfo,
|
server_info: &ServerInfo,
|
||||||
|
map_img: GraphicId,
|
||||||
|
possible_starting_sites: Vec<SiteInfo>,
|
||||||
|
world_sz: Vec2<u32>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
||||||
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
|
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
|
||||||
@ -339,6 +373,9 @@ impl Controls {
|
|||||||
mode: Mode::select(Some(InfoContent::LoadingCharacters)),
|
mode: Mode::select(Some(InfoContent::LoadingCharacters)),
|
||||||
selected,
|
selected,
|
||||||
default_name,
|
default_name,
|
||||||
|
map_img,
|
||||||
|
possible_starting_sites,
|
||||||
|
world_sz,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +430,35 @@ impl Controls {
|
|||||||
])
|
])
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
|
|
||||||
|
let mut warning_container = if let Some(mismatched_version) =
|
||||||
|
&self.server_mismatched_version
|
||||||
|
{
|
||||||
|
let warning = Text::<IcedRenderer>::new(format!(
|
||||||
|
"{}\n{}: {} {}: {}",
|
||||||
|
i18n.get_msg("char_selection-version_mismatch"),
|
||||||
|
i18n.get_msg("main-login-server_version"),
|
||||||
|
mismatched_version,
|
||||||
|
i18n.get_msg("main-login-client_version"),
|
||||||
|
*common::util::GIT_HASH
|
||||||
|
))
|
||||||
|
.size(self.fonts.cyri.scale(18))
|
||||||
|
.color(iced::Color::from_rgb(1.0, 0.0, 0.0))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.horizontal_alignment(HorizontalAlignment::Center);
|
||||||
|
Some(
|
||||||
|
Container::new(
|
||||||
|
Container::new(Row::with_children(vec![warning.into()]).width(Length::Fill))
|
||||||
|
.style(style::container::Style::color(Rgba::new(0, 0, 0, 217)))
|
||||||
|
.padding(12)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.center_x(),
|
||||||
|
)
|
||||||
|
.padding(16),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let content = match &mut self.mode {
|
let content = match &mut self.mode {
|
||||||
Mode::Select {
|
Mode::Select {
|
||||||
ref mut info_content,
|
ref mut info_content,
|
||||||
@ -669,8 +735,8 @@ impl Controls {
|
|||||||
let left_column = Column::with_children(vec![server.into(), characters.into()])
|
let left_column = Column::with_children(vec![server.into(), characters.into()])
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.width(Length::Units(322)) // TODO: see if we can get iced to work with settings below
|
.width(Length::Units(322)) // TODO: see if we can get iced to work with settings below
|
||||||
//.max_width(360)
|
// .max_width(360)
|
||||||
//.width(Length::Fill)
|
// .width(Length::Fill)
|
||||||
.height(Length::Fill);
|
.height(Length::Fill);
|
||||||
|
|
||||||
let top = Row::with_children(vec![
|
let top = Row::with_children(vec![
|
||||||
@ -831,7 +897,8 @@ impl Controls {
|
|||||||
inventory: _,
|
inventory: _,
|
||||||
mainhand,
|
mainhand,
|
||||||
offhand: _,
|
offhand: _,
|
||||||
ref mut scroll,
|
ref mut left_scroll,
|
||||||
|
ref mut right_scroll,
|
||||||
ref mut body_type_buttons,
|
ref mut body_type_buttons,
|
||||||
ref mut species_buttons,
|
ref mut species_buttons,
|
||||||
ref mut tool_buttons,
|
ref mut tool_buttons,
|
||||||
@ -841,7 +908,10 @@ impl Controls {
|
|||||||
ref mut create_button,
|
ref mut create_button,
|
||||||
ref mut rand_character_button,
|
ref mut rand_character_button,
|
||||||
ref mut rand_name_button,
|
ref mut rand_name_button,
|
||||||
|
ref mut prev_starting_site_button,
|
||||||
|
ref mut next_starting_site_button,
|
||||||
character_id,
|
character_id,
|
||||||
|
start_site_idx,
|
||||||
} => {
|
} => {
|
||||||
let unselected_style = style::button::Style::new(imgs.icon_border)
|
let unselected_style = style::button::Style::new(imgs.icon_border)
|
||||||
.hover_image(imgs.icon_border_mo)
|
.hover_image(imgs.icon_border_mo)
|
||||||
@ -1070,6 +1140,31 @@ impl Controls {
|
|||||||
// Height of interactable area
|
// Height of interactable area
|
||||||
const SLIDER_HEIGHT: u16 = 30;
|
const SLIDER_HEIGHT: u16 = 30;
|
||||||
|
|
||||||
|
fn starter_slider<'a>(
|
||||||
|
text: String,
|
||||||
|
size: u16,
|
||||||
|
state: &'a mut slider::State,
|
||||||
|
max: u32,
|
||||||
|
selected_val: u32,
|
||||||
|
on_change: impl 'static + Fn(u32) -> Message,
|
||||||
|
imgs: &Imgs,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
Column::with_children(vec![
|
||||||
|
Text::new(text).size(size).into(),
|
||||||
|
Slider::new(state, 0..=max, selected_val, on_change)
|
||||||
|
.height(SLIDER_HEIGHT)
|
||||||
|
.style(style::slider::Style::images(
|
||||||
|
imgs.slider_indicator,
|
||||||
|
imgs.slider_range,
|
||||||
|
SLIDER_BAR_PAD,
|
||||||
|
SLIDER_CURSOR_SIZE,
|
||||||
|
SLIDER_BAR_HEIGHT,
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.align_items(Align::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
fn char_slider<'a>(
|
fn char_slider<'a>(
|
||||||
text: String,
|
text: String,
|
||||||
state: &'a mut slider::State,
|
state: &'a mut slider::State,
|
||||||
@ -1115,19 +1210,21 @@ impl Controls {
|
|||||||
.into(),
|
.into(),
|
||||||
// "Disabled" slider
|
// "Disabled" slider
|
||||||
// TODO: add iced support for disabled sliders (like buttons)
|
// TODO: add iced support for disabled sliders (like buttons)
|
||||||
Slider::new(state, 0..=max, selected_val, |_| Message::DoNothing)
|
Slider::new(state, 0..=max.into(), selected_val.into(), |_| {
|
||||||
.height(SLIDER_HEIGHT)
|
Message::DoNothing
|
||||||
.style(style::slider::Style {
|
})
|
||||||
cursor: style::slider::Cursor::Color(Rgba::zero()),
|
.height(SLIDER_HEIGHT)
|
||||||
bar: style::slider::Bar::Image(
|
.style(style::slider::Style {
|
||||||
imgs.slider_range,
|
cursor: style::slider::Cursor::Color(Rgba::zero()),
|
||||||
Rgba::from_translucent(255, 51),
|
bar: style::slider::Bar::Image(
|
||||||
SLIDER_BAR_PAD,
|
imgs.slider_range,
|
||||||
),
|
Rgba::from_translucent(255, 51),
|
||||||
labels: false,
|
SLIDER_BAR_PAD,
|
||||||
..Default::default()
|
),
|
||||||
})
|
labels: false,
|
||||||
.into(),
|
..Default::default()
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
])
|
])
|
||||||
.align_items(Align::Center)
|
.align_items(Align::Center)
|
||||||
.into()
|
.into()
|
||||||
@ -1213,7 +1310,7 @@ impl Controls {
|
|||||||
tooltip::text(&tooltip_text, tooltip_style)
|
tooltip::text(&tooltip_text, tooltip_style)
|
||||||
});
|
});
|
||||||
|
|
||||||
let column_content = vec![
|
let left_column_content = vec![
|
||||||
body_type.into(),
|
body_type.into(),
|
||||||
tool.into(),
|
tool.into(),
|
||||||
species.into(),
|
species.into(),
|
||||||
@ -1221,47 +1318,215 @@ impl Controls {
|
|||||||
rand_character.into(),
|
rand_character.into(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let left_column = Container::new(
|
let right_column_content = if character_id.is_none() {
|
||||||
Scrollable::new(scroll)
|
let site_slider = starter_slider(
|
||||||
.push(
|
i18n.get_msg("char_selection-starting_site").into_owned(),
|
||||||
Column::with_children(column_content)
|
30,
|
||||||
.align_items(Align::Center)
|
&mut sliders.starting_site,
|
||||||
.width(Length::Fill)
|
self.possible_starting_sites.len() as u32 - 1,
|
||||||
.spacing(5),
|
*start_site_idx as u32,
|
||||||
)
|
|x| Message::StartingSite(x as usize),
|
||||||
.padding(5)
|
imgs,
|
||||||
.width(Length::Fill)
|
);
|
||||||
.align_items(Align::Center)
|
let map_sz = Vec2::new(500, 500);
|
||||||
.style(style::scrollable::Style {
|
let map_img = Image::new(self.map_img)
|
||||||
track: None,
|
.height(Length::Units(map_sz.x))
|
||||||
scroller: style::scrollable::Scroller::Color(UI_MAIN),
|
.width(Length::Units(map_sz.y));
|
||||||
}),
|
let site_name = Text::new(
|
||||||
)
|
self.possible_starting_sites[*start_site_idx]
|
||||||
.width(Length::Units(320)) // TODO: see if we can get iced to work with settings below
|
.name
|
||||||
//.max_width(360)
|
.as_deref()
|
||||||
//.width(Length::Fill)
|
.unwrap_or("Unknown")
|
||||||
.height(Length::Fill);
|
)
|
||||||
|
.horizontal_alignment(HorizontalAlignment::Left)
|
||||||
|
.color(Color::from_rgb(131.0, 102.0, 0.0))
|
||||||
|
/* .stroke(Stroke {
|
||||||
|
color: Color::WHITE,
|
||||||
|
width: 1.0,
|
||||||
|
}) */;
|
||||||
|
//TODO: Add text-outline here whenever we updated iced to a version supporting
|
||||||
|
// this
|
||||||
|
|
||||||
let left_column = Column::with_children(vec![
|
let map = if let Some(info) = self.possible_starting_sites.get(*start_site_idx)
|
||||||
Container::new(left_column)
|
{
|
||||||
.style(style::container::Style::color(Rgba::from_translucent(
|
let pos_frac = info
|
||||||
0,
|
.wpos
|
||||||
BANNER_ALPHA,
|
.map2(self.world_sz * TerrainChunkSize::RECT_SIZE, |e, sz| {
|
||||||
)))
|
e as f32 / sz as f32
|
||||||
.width(Length::Units(320))
|
});
|
||||||
.center_x()
|
let point = Vec2::new(pos_frac.x, 1.0 - pos_frac.y)
|
||||||
.into(),
|
.map2(map_sz, |e, sz| e * sz as f32 - 12.0);
|
||||||
Image::new(imgs.frame_bottom)
|
let marker_img = Image::new(imgs.town_marker)
|
||||||
.height(Length::Units(40))
|
.height(Length::Units(27))
|
||||||
.width(Length::Units(320))
|
.width(Length::Units(16));
|
||||||
.color(Rgba::from_translucent(0, BANNER_ALPHA))
|
let marker_content: Column<Message, IcedRenderer> = Column::new()
|
||||||
.into(),
|
.spacing(2)
|
||||||
])
|
.push(site_name)
|
||||||
.height(Length::Fill);
|
.push(marker_img)
|
||||||
|
.align_items(Align::Center);
|
||||||
|
|
||||||
|
Overlay::new(
|
||||||
|
Container::new(marker_content)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.center_y(),
|
||||||
|
map_img,
|
||||||
|
)
|
||||||
|
.over_position(iced::Point::new(point.x, point.y - 34.0))
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
map_img.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.possible_starting_sites.is_empty() {
|
||||||
|
vec![map]
|
||||||
|
} else {
|
||||||
|
let site_buttons = Row::with_children(vec![
|
||||||
|
neat_button(
|
||||||
|
prev_starting_site_button,
|
||||||
|
i18n.get_msg("char_selection-starting_site_prev")
|
||||||
|
.into_owned(),
|
||||||
|
FILL_FRAC_ONE,
|
||||||
|
button_style,
|
||||||
|
Some(Message::PrevStartingSite),
|
||||||
|
),
|
||||||
|
neat_button(
|
||||||
|
next_starting_site_button,
|
||||||
|
i18n.get_msg("char_selection-starting_site_next")
|
||||||
|
.into_owned(),
|
||||||
|
FILL_FRAC_ONE,
|
||||||
|
button_style,
|
||||||
|
Some(Message::NextStartingSite),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.max_height(60)
|
||||||
|
.padding(15)
|
||||||
|
.into();
|
||||||
|
// Todo: use this to change the site icon if we use different starting site
|
||||||
|
// types
|
||||||
|
/* let site_kind = Text::new(i18n
|
||||||
|
.get_msg_ctx("char_selection-starting_site_kind", &i18n::fluent_args! {
|
||||||
|
"kind" => match self.possible_starting_sites[*start_site_idx].kind {
|
||||||
|
SiteKind::Town => i18n.get_msg("hud-map-town").into_owned(),
|
||||||
|
SiteKind::Castle => i18n.get_msg("hud-map-castle").into_owned(),
|
||||||
|
SiteKind::Bridge => i18n.get_msg("hud-map-bridge").into_owned(),
|
||||||
|
_ => "Unknown".to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.into_owned())
|
||||||
|
.size(fonts.cyri.scale(SLIDER_TEXT_SIZE))
|
||||||
|
.into(); */
|
||||||
|
|
||||||
|
vec![site_slider, map, site_buttons]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we're editing an existing character, don't display the world column
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let column_left = |column_content, scroll| {
|
||||||
|
let column = Container::new(
|
||||||
|
Scrollable::new(scroll)
|
||||||
|
.push(
|
||||||
|
Column::with_children(column_content)
|
||||||
|
.align_items(Align::Center)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.spacing(5)
|
||||||
|
.padding(5),
|
||||||
|
)
|
||||||
|
.padding(5)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.align_items(Align::Center)
|
||||||
|
.style(style::scrollable::Style {
|
||||||
|
track: None,
|
||||||
|
scroller: style::scrollable::Scroller::Color(UI_MAIN),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.width(Length::Units(320)) // TODO: see if we can get iced to work with settings below
|
||||||
|
// .max_width(360)
|
||||||
|
// .width(Length::Fill)
|
||||||
|
.height(Length::Fill);
|
||||||
|
|
||||||
|
Column::with_children(vec![
|
||||||
|
Container::new(column)
|
||||||
|
.style(style::container::Style::color(Rgba::from_translucent(
|
||||||
|
0,
|
||||||
|
BANNER_ALPHA,
|
||||||
|
)))
|
||||||
|
.width(Length::Units(320))
|
||||||
|
.center_x()
|
||||||
|
.into(),
|
||||||
|
Image::new(imgs.frame_bottom)
|
||||||
|
.height(Length::Units(40))
|
||||||
|
.width(Length::Units(320))
|
||||||
|
.color(Rgba::from_translucent(0, BANNER_ALPHA))
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.height(Length::Fill)
|
||||||
|
};
|
||||||
|
let column_right = |column_content, scroll| {
|
||||||
|
let column = Container::new(
|
||||||
|
Scrollable::new(scroll)
|
||||||
|
.push(
|
||||||
|
Column::with_children(column_content)
|
||||||
|
.align_items(Align::Center)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.spacing(5)
|
||||||
|
.padding(5),
|
||||||
|
)
|
||||||
|
.padding(5)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.align_items(Align::Center)
|
||||||
|
.style(style::scrollable::Style {
|
||||||
|
track: None,
|
||||||
|
scroller: style::scrollable::Scroller::Color(UI_MAIN),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.width(Length::Units(520)) // TODO: see if we can get iced to work with settings below
|
||||||
|
// .max_width(360)
|
||||||
|
// .width(Length::Fill)
|
||||||
|
.height(Length::Fill);
|
||||||
|
if character_id.is_none() {
|
||||||
|
Column::with_children(vec![
|
||||||
|
Container::new(column)
|
||||||
|
.style(style::container::Style::color(Rgba::from_translucent(
|
||||||
|
0,
|
||||||
|
BANNER_ALPHA,
|
||||||
|
)))
|
||||||
|
.width(Length::Units(520))
|
||||||
|
.center_x()
|
||||||
|
.into(),
|
||||||
|
Image::new(imgs.frame_bottom)
|
||||||
|
.height(Length::Units(40))
|
||||||
|
.width(Length::Units(520))
|
||||||
|
.color(Rgba::from_translucent(0, BANNER_ALPHA))
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.height(Length::Fill)
|
||||||
|
} else {
|
||||||
|
Column::with_children(vec![Container::new(column).into()])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mouse_area =
|
||||||
|
MouseDetector::new(&mut self.mouse_detector, Length::Fill, Length::Fill);
|
||||||
|
|
||||||
let top = Row::with_children(vec![
|
let top = Row::with_children(vec![
|
||||||
left_column.into(),
|
column_left(left_column_content, left_scroll).into(),
|
||||||
MouseDetector::new(&mut self.mouse_detector, Length::Fill, Length::Fill).into(),
|
Column::with_children(
|
||||||
|
if let Some(warning_container) = warning_container.take() {
|
||||||
|
vec![warning_container.into(), mouse_area.into()]
|
||||||
|
} else {
|
||||||
|
vec![mouse_area.into()]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into(),
|
||||||
|
column_right(right_column_content, right_scroll)
|
||||||
|
.width(Length::Units(520))
|
||||||
|
.into(),
|
||||||
])
|
])
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
@ -1373,46 +1638,20 @@ impl Controls {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: There is probably a better way to conditionally add in the warning box
|
let children = if let Some(warning_container) = warning_container {
|
||||||
// here
|
vec![top_text.into(), warning_container.into(), content]
|
||||||
if let Some(mismatched_version) = &self.server_mismatched_version {
|
|
||||||
let warning = Text::<IcedRenderer>::new(format!(
|
|
||||||
"{}\n{}: {} {}: {}",
|
|
||||||
i18n.get_msg("char_selection-version_mismatch"),
|
|
||||||
i18n.get_msg("main-login-server_version"),
|
|
||||||
mismatched_version,
|
|
||||||
i18n.get_msg("main-login-client_version"),
|
|
||||||
*common::util::GIT_HASH
|
|
||||||
))
|
|
||||||
.size(self.fonts.cyri.scale(18))
|
|
||||||
.color(iced::Color::from_rgb(1.0, 0.0, 0.0))
|
|
||||||
.width(Length::Fill)
|
|
||||||
.horizontal_alignment(HorizontalAlignment::Center);
|
|
||||||
let warning_container =
|
|
||||||
Container::new(Row::with_children(vec![warning.into()]).width(Length::Fill))
|
|
||||||
.style(style::container::Style::color(Rgba::new(0, 0, 0, 217)))
|
|
||||||
.padding(12)
|
|
||||||
.center_x()
|
|
||||||
.width(Length::Fill);
|
|
||||||
|
|
||||||
Container::new(
|
|
||||||
Column::with_children(vec![top_text.into(), warning_container.into(), content])
|
|
||||||
.spacing(3)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Fill),
|
|
||||||
)
|
|
||||||
.padding(3)
|
|
||||||
.into()
|
|
||||||
} else {
|
} else {
|
||||||
Container::new(
|
vec![top_text.into(), content]
|
||||||
Column::with_children(vec![top_text.into(), content])
|
};
|
||||||
.spacing(3)
|
|
||||||
.width(Length::Fill)
|
Container::new(
|
||||||
.height(Length::Fill),
|
Column::with_children(children)
|
||||||
)
|
.spacing(3)
|
||||||
.padding(3)
|
.width(Length::Fill)
|
||||||
.into()
|
.height(Length::Fill),
|
||||||
}
|
)
|
||||||
|
.padding(3)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message, events: &mut Vec<Event>, characters: &[CharacterItem]) {
|
fn update(&mut self, message: Message, events: &mut Vec<Event>, characters: &[CharacterItem]) {
|
||||||
@ -1516,6 +1755,7 @@ impl Controls {
|
|||||||
body,
|
body,
|
||||||
mainhand,
|
mainhand,
|
||||||
offhand,
|
offhand,
|
||||||
|
start_site_idx,
|
||||||
..
|
..
|
||||||
} = &self.mode
|
} = &self.mode
|
||||||
{
|
{
|
||||||
@ -1524,6 +1764,10 @@ impl Controls {
|
|||||||
mainhand: mainhand.map(String::from),
|
mainhand: mainhand.map(String::from),
|
||||||
offhand: offhand.map(String::from),
|
offhand: offhand.map(String::from),
|
||||||
body: comp::Body::Humanoid(*body),
|
body: comp::Body::Humanoid(*body),
|
||||||
|
start_site: self
|
||||||
|
.possible_starting_sites
|
||||||
|
.get(*start_site_idx)
|
||||||
|
.map(|info| info.id),
|
||||||
});
|
});
|
||||||
self.mode = Mode::select(Some(InfoContent::CreatingCharacter));
|
self.mode = Mode::select(Some(InfoContent::CreatingCharacter));
|
||||||
}
|
}
|
||||||
@ -1643,6 +1887,29 @@ impl Controls {
|
|||||||
body.validate();
|
body.validate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Message::StartingSite(idx) => {
|
||||||
|
if let Mode::CreateOrEdit { start_site_idx, .. } = &mut self.mode {
|
||||||
|
*start_site_idx = idx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Message::PrevStartingSite => {
|
||||||
|
if let Mode::CreateOrEdit { start_site_idx, .. } = &mut self.mode {
|
||||||
|
if !self.possible_starting_sites.is_empty() {
|
||||||
|
*start_site_idx = (*start_site_idx + self.possible_starting_sites.len()
|
||||||
|
- 1)
|
||||||
|
% self.possible_starting_sites.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Message::NextStartingSite => {
|
||||||
|
if let Mode::CreateOrEdit { start_site_idx, .. } = &mut self.mode {
|
||||||
|
if !self.possible_starting_sites.is_empty() {
|
||||||
|
*start_site_idx =
|
||||||
|
(*start_site_idx + self.possible_starting_sites.len() + 1)
|
||||||
|
% self.possible_starting_sites.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1707,6 +1974,17 @@ impl CharSelectionUi {
|
|||||||
selected_character,
|
selected_character,
|
||||||
default_name,
|
default_name,
|
||||||
client.server_info(),
|
client.server_info(),
|
||||||
|
ui.add_graphic(Graphic::Image(
|
||||||
|
Arc::clone(client.world_data().topo_map_image()),
|
||||||
|
Some(default_water_color()),
|
||||||
|
)),
|
||||||
|
client.sites()
|
||||||
|
.values()
|
||||||
|
// TODO: Enforce this server-side and add some way to customise it?
|
||||||
|
.filter(|info| matches!(&info.site.kind, SiteKind::Town /*| SiteKind::Castle | SiteKind::Bridge*/))
|
||||||
|
.map(|info| info.site.clone())
|
||||||
|
.collect(),
|
||||||
|
client.world_data().chunk_size().as_(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -1814,4 +2092,5 @@ struct Sliders {
|
|||||||
eye_color: slider::State,
|
eye_color: slider::State,
|
||||||
accessory: slider::State,
|
accessory: slider::State,
|
||||||
beard: slider::State,
|
beard: slider::State,
|
||||||
|
starting_site: slider::State,
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ pub struct Overlay<'a, M, R: Renderer> {
|
|||||||
vertical_alignment: Align,
|
vertical_alignment: Align,
|
||||||
over: Element<'a, M, R>,
|
over: Element<'a, M, R>,
|
||||||
under: Element<'a, M, R>,
|
under: Element<'a, M, R>,
|
||||||
|
pos: Option<Point>,
|
||||||
// add style etc as needed
|
// add style etc as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +42,16 @@ where
|
|||||||
vertical_alignment: Align::Start,
|
vertical_alignment: Align::Start,
|
||||||
over: over.into(),
|
over: over.into(),
|
||||||
under: under.into(),
|
under: under.into(),
|
||||||
|
pos: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn over_position(mut self, pos: Point) -> Self {
|
||||||
|
self.pos = Some(pos);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn padding<P: Into<Padding>>(mut self, pad: P) -> Self {
|
pub fn padding<P: Into<Padding>>(mut self, pad: P) -> Self {
|
||||||
self.padding = pad.into();
|
self.padding = pad.into();
|
||||||
@ -130,10 +138,10 @@ where
|
|||||||
.pad(self.padding),
|
.pad(self.padding),
|
||||||
);
|
);
|
||||||
|
|
||||||
over.move_to(Point::new(
|
over.move_to(
|
||||||
self.padding.left.into(),
|
self.pos
|
||||||
self.padding.top.into(),
|
.unwrap_or_else(|| Point::new(self.padding.left.into(), self.padding.top.into())),
|
||||||
));
|
);
|
||||||
over.align(self.horizontal_alignment, self.vertical_alignment, size);
|
over.align(self.horizontal_alignment, self.vertical_alignment, size);
|
||||||
|
|
||||||
layout::Node::with_children(size, vec![over, under])
|
layout::Node::with_children(size, vec![over, under])
|
||||||
|
@ -219,6 +219,11 @@ impl World {
|
|||||||
|
|
||||||
pub fn sample_blocks(&self) -> BlockGen { BlockGen::new(ColumnGen::new(&self.sim)) }
|
pub fn sample_blocks(&self) -> BlockGen { BlockGen::new(ColumnGen::new(&self.sim)) }
|
||||||
|
|
||||||
|
/// Find a position that's accessible to a player at the given world
|
||||||
|
/// position by searching blocks vertically.
|
||||||
|
///
|
||||||
|
/// If `ascending` is `true`, we try to find the highest accessible position
|
||||||
|
/// instead of the lowest.
|
||||||
pub fn find_accessible_pos(
|
pub fn find_accessible_pos(
|
||||||
&self,
|
&self,
|
||||||
index: IndexRef,
|
index: IndexRef,
|
||||||
|
Loading…
Reference in New Issue
Block a user