mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Correct localisation of numeric values, use coordinate conversions in rtsim
This commit is contained in:
parent
0c688a9b50
commit
16e3227f78
@ -19,11 +19,11 @@ use std::{borrow::Cow, io};
|
||||
use assets::{source::DirEntry, AssetExt, AssetGuard, AssetHandle, ReloadWatcher, SharedString};
|
||||
use tracing::warn;
|
||||
// Re-export because I don't like prefix
|
||||
use common::comp::Content;
|
||||
use common::comp::{Content, LocalizationArg};
|
||||
use common_assets as assets;
|
||||
|
||||
// Re-export for argument creation
|
||||
pub use fluent::fluent_args;
|
||||
pub use fluent::{fluent_args, FluentValue};
|
||||
pub use fluent_bundle::FluentArgs;
|
||||
|
||||
/// The reference language, aka the more up-to-date localization data.
|
||||
@ -332,7 +332,7 @@ impl LocalizationGuard {
|
||||
})
|
||||
}
|
||||
|
||||
/// Localize the given context.
|
||||
/// Localize the given content.
|
||||
pub fn get_content(&self, content: &Content) -> String {
|
||||
match content {
|
||||
Content::Plain(text) => text.clone(),
|
||||
@ -342,7 +342,14 @@ impl LocalizationGuard {
|
||||
*seed,
|
||||
&args
|
||||
.iter()
|
||||
.map(|(k, content)| (k, self.get_content(content)))
|
||||
.map(|(k, arg)| {
|
||||
(k, match arg {
|
||||
LocalizationArg::Content(content) => {
|
||||
FluentValue::String(self.get_content(content).into())
|
||||
},
|
||||
LocalizationArg::Nat(n) => FluentValue::from(n),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.into_owned(),
|
||||
|
@ -199,20 +199,56 @@ pub enum Content {
|
||||
/// deterministic (but pseudorandom) localised output
|
||||
seed: u16,
|
||||
/// i18n arguments
|
||||
args: HashMap<String, Content>,
|
||||
args: HashMap<String, LocalizationArg>,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Plain` explicit (to discourage it)
|
||||
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||
impl From<String> for Content {
|
||||
fn from(text: String) -> Self { Self::Plain(text) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Plain` explicit (to discourage it)
|
||||
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
|
||||
impl<'a> From<&'a str> for Content {
|
||||
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
||||
}
|
||||
|
||||
/// A localisation argument for localised content (see [`Content::Localized`]).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LocalizationArg {
|
||||
/// The localisation argument is itself a section of content.
|
||||
///
|
||||
/// Note that this allows [`Content`] to recursively refer to itself. It may
|
||||
/// be tempting to decide to parameterise everything, having dialogue
|
||||
/// generated with a compact tree. "It's simpler!", you might say. False.
|
||||
/// Over-parameterisation is an anti-pattern that hurts translators. Where
|
||||
/// possible, prefer fewer levels of nesting unless doing so would
|
||||
/// result in an intractably larger number of combinations. See [here](https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers#prefer-wet-over-dry) for the
|
||||
/// guidance provided by the docs for `fluent`, the localisation library
|
||||
/// used by clients.
|
||||
Content(Content),
|
||||
/// The localisation argument is a natural number
|
||||
Nat(u64),
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl From<String> for LocalizationArg {
|
||||
fn from(text: String) -> Self { Self::Content(Content::Plain(text)) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl<'a> From<&'a str> for LocalizationArg {
|
||||
fn from(text: &'a str) -> Self { Self::Content(Content::Plain(text.to_string())) }
|
||||
}
|
||||
|
||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||
// discourage it)
|
||||
impl From<u64> for LocalizationArg {
|
||||
fn from(n: u64) -> Self { Self::Nat(n) }
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn localized(key: impl ToString) -> Self {
|
||||
Self::Localized {
|
||||
@ -222,7 +258,7 @@ impl Content {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn localized_with_args<'a, A: Into<Content>>(
|
||||
pub fn localized_with_args<'a, A: Into<LocalizationArg>>(
|
||||
key: impl ToString,
|
||||
args: impl IntoIterator<Item = (&'a str, A)>,
|
||||
) -> Self {
|
||||
@ -341,6 +377,8 @@ impl<G> GenericChatMsg<G> {
|
||||
|
||||
pub fn content(&self) -> &Content { &self.content }
|
||||
|
||||
pub fn into_content(self) -> Content { self.content }
|
||||
|
||||
pub fn set_content(&mut self, content: Content) { self.content = content; }
|
||||
}
|
||||
|
||||
|
@ -75,8 +75,8 @@ pub use self::{
|
||||
},
|
||||
character_state::{CharacterActivity, CharacterState, StateUpdate},
|
||||
chat::{
|
||||
ChatMode, ChatMsg, ChatType, Content, Faction, SpeechBubble, SpeechBubbleType,
|
||||
UnresolvedChatMsg,
|
||||
ChatMode, ChatMsg, ChatType, Content, Faction, LocalizationArg, SpeechBubble,
|
||||
SpeechBubbleType, UnresolvedChatMsg,
|
||||
},
|
||||
combo::Combo,
|
||||
controller::{
|
||||
|
@ -84,7 +84,9 @@ pub trait CoordinateConversions {
|
||||
|
||||
impl CoordinateConversions for Vec2<i32> {
|
||||
#[inline]
|
||||
fn wpos_to_cpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e / sz as i32) }
|
||||
fn wpos_to_cpos(&self) -> Self {
|
||||
self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cpos_to_wpos(&self) -> Self { self.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * sz as i32) }
|
||||
|
@ -12,8 +12,7 @@ use common::{
|
||||
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, SiteId, VehicleId,
|
||||
},
|
||||
store::Id,
|
||||
terrain::TerrainChunkSize,
|
||||
vol::RectVolSize,
|
||||
terrain::CoordinateConversions,
|
||||
};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rand::prelude::*;
|
||||
@ -349,12 +348,7 @@ impl Npcs {
|
||||
wpos: Vec3<f32>,
|
||||
radius: f32,
|
||||
) -> impl Iterator<Item = Actor> + '_ {
|
||||
let chunk_pos = wpos
|
||||
.xy()
|
||||
.as_::<i32>()
|
||||
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
||||
e.div_euclid(sz)
|
||||
});
|
||||
let chunk_pos = wpos.xy().as_().wpos_to_cpos();
|
||||
let r_sqr = radius * radius;
|
||||
LOCALITY
|
||||
.into_iter()
|
||||
|
@ -16,9 +16,8 @@ use common::{
|
||||
rtsim::{ChunkResource, Profession, SiteId},
|
||||
spiral::Spiral2d,
|
||||
store::Id,
|
||||
terrain::{SiteKindMeta, TerrainChunkSize},
|
||||
terrain::{CoordinateConversions, SiteKindMeta, TerrainChunkSize},
|
||||
time::DayPeriod,
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use fxhash::FxHasher64;
|
||||
use itertools::{Either, Itertools};
|
||||
@ -333,7 +332,7 @@ where
|
||||
let wpos_site = |wpos: Vec2<f32>| {
|
||||
ctx.world
|
||||
.sim()
|
||||
.get(wpos.as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_())
|
||||
.get(wpos.as_().wpos_to_cpos())
|
||||
.and_then(|chunk| chunk.sites.first().copied())
|
||||
};
|
||||
|
||||
@ -568,7 +567,7 @@ fn hunt_animals() -> impl Action {
|
||||
}
|
||||
|
||||
fn find_forest(ctx: &mut NpcCtx) -> Option<Vec2<f32>> {
|
||||
let chunk_pos = ctx.npc.wpos.xy().as_() / TerrainChunkSize::RECT_SIZE.as_();
|
||||
let chunk_pos = ctx.npc.wpos.xy().as_().wpos_to_cpos();
|
||||
Spiral2d::new()
|
||||
.skip(ctx.rng.gen_range(1..=8))
|
||||
.take(49)
|
||||
@ -801,8 +800,7 @@ fn chunk_path(
|
||||
.filter_map(|p| Some((p, chunk_height(p)?)))
|
||||
},
|
||||
|(p0, h0), (p1, h1)| {
|
||||
let diff =
|
||||
((p0 - p1).as_() * TerrainChunkSize::RECT_SIZE.as_()).with_z((h0 - h1) as f32);
|
||||
let diff = (p0 - p1).as_().cpos_to_wpos().with_z((h0 - h1) as f32);
|
||||
|
||||
diff.magnitude_squared()
|
||||
},
|
||||
@ -841,9 +839,8 @@ fn pilot() -> impl Action {
|
||||
})
|
||||
.choose(&mut ctx.rng);
|
||||
if let Some((_id, site)) = site {
|
||||
let start_chunk =
|
||||
ctx.npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||
let end_chunk = site.wpos / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||
let start_chunk = ctx.npc.wpos.xy().as_().wpos_to_cpos();
|
||||
let end_chunk = site.wpos.wpos_to_cpos();
|
||||
chunk_path(start_chunk, end_chunk, |chunk| {
|
||||
ctx.world
|
||||
.sim()
|
||||
@ -861,7 +858,7 @@ fn pilot() -> impl Action {
|
||||
fn captain() -> impl Action {
|
||||
// For now just randomly travel the sea
|
||||
now(|ctx| {
|
||||
let chunk = ctx.npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||
let chunk = ctx.npc.wpos.xy().as_().wpos_to_cpos();
|
||||
if let Some(chunk) = NEIGHBORS
|
||||
.into_iter()
|
||||
.map(|neighbor| chunk + neighbor)
|
||||
|
@ -6,8 +6,7 @@ use crate::{
|
||||
use common::{
|
||||
comp::{self, Body},
|
||||
rtsim::{Actor, NpcAction, NpcActivity, Personality},
|
||||
terrain::TerrainChunkSize,
|
||||
vol::RectVolSize,
|
||||
terrain::CoordinateConversions,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaChaRng;
|
||||
@ -180,11 +179,7 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
||||
| common::comp::ship::Body::AirBalloon => true,
|
||||
common::comp::ship::Body::SailBoat
|
||||
| common::comp::ship::Body::Galleon => {
|
||||
let chunk_pos =
|
||||
wpos.xy().as_::<i32>().map2(
|
||||
TerrainChunkSize::RECT_SIZE.as_::<i32>(),
|
||||
|e, sz| e.div_euclid(sz),
|
||||
);
|
||||
let chunk_pos = wpos.xy().as_().wpos_to_cpos();
|
||||
ctx.world
|
||||
.sim()
|
||||
.get(chunk_pos)
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
event::{EventCtx, OnDeath, OnSetup, OnTick},
|
||||
RtState, Rule, RuleError,
|
||||
};
|
||||
use common::{grid::Grid, rtsim::Actor, terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use common::{grid::Grid, rtsim::Actor, terrain::CoordinateConversions};
|
||||
|
||||
pub struct SyncNpcs;
|
||||
|
||||
@ -45,7 +45,7 @@ fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
|
||||
let data = &mut *ctx.state.data_mut();
|
||||
// Update vehicle grid cells
|
||||
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
|
||||
let chunk_pos = vehicle.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||
let chunk_pos = vehicle.wpos.xy().as_().wpos_to_cpos();
|
||||
if vehicle.chunk_pos != Some(chunk_pos) {
|
||||
if let Some(cell) = vehicle
|
||||
.chunk_pos
|
||||
@ -66,14 +66,7 @@ fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
|
||||
npc.current_site = ctx
|
||||
.world
|
||||
.sim()
|
||||
.get(
|
||||
npc.wpos
|
||||
.xy()
|
||||
.as_::<i32>()
|
||||
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
||||
e.div_euclid(sz)
|
||||
}),
|
||||
)
|
||||
.get(npc.wpos.xy().as_().wpos_to_cpos())
|
||||
.and_then(|chunk| {
|
||||
chunk
|
||||
.sites
|
||||
@ -99,13 +92,7 @@ fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
|
||||
}
|
||||
|
||||
// Update the NPC's grid cell
|
||||
let chunk_pos = npc
|
||||
.wpos
|
||||
.xy()
|
||||
.as_::<i32>()
|
||||
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
||||
e.div_euclid(sz)
|
||||
});
|
||||
let chunk_pos = npc.wpos.xy().as_().wpos_to_cpos();
|
||||
if npc.chunk_pos != Some(chunk_pos) {
|
||||
if let Some(cell) = npc
|
||||
.chunk_pos
|
||||
|
@ -1515,6 +1515,8 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Pass a localisation key instead of `Content` to avoid allocating if
|
||||
// we're not permitted to speak.
|
||||
pub fn chat_npc_if_allowed_to_speak(
|
||||
&self,
|
||||
msg: Content,
|
||||
|
@ -201,7 +201,10 @@ impl RtSim {
|
||||
debug!("Saving rtsim data...");
|
||||
|
||||
// Create the save thread if it doesn't already exist
|
||||
// TODO: Use the slow job pool eventually
|
||||
// We're not using the slow job pool here for two reasons:
|
||||
// 1) The thread is mostly blocked on IO, not compute
|
||||
// 2) We need to synchronise saves to ensure monotonicity, which slow jobs
|
||||
// aren't designed to allow
|
||||
let (tx, _) = self.save_thread.get_or_insert_with(|| {
|
||||
trace!("Starting rtsim data save thread...");
|
||||
let (tx, rx) = unbounded();
|
||||
|
@ -9,9 +9,8 @@ use common::{
|
||||
resources::{DeltaTime, Time, TimeOfDay},
|
||||
rtsim::{Actor, RtSimEntity, RtSimVehicle},
|
||||
slowjob::SlowJobPool,
|
||||
terrain::{CoordinateConversions, TerrainChunkSize},
|
||||
terrain::CoordinateConversions,
|
||||
trade::{Good, SiteInformation},
|
||||
vol::RectVolSize,
|
||||
LoadoutBuilder,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
@ -236,13 +235,7 @@ impl<'a> System<'a> for Sys {
|
||||
data.npcs.character_map.clear();
|
||||
for (presence, wpos) in (&presences, &positions).join() {
|
||||
if let PresenceKind::Character(character) = &presence.kind {
|
||||
let chunk_pos = wpos
|
||||
.0
|
||||
.xy()
|
||||
.as_::<i32>()
|
||||
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
||||
e.div_euclid(sz)
|
||||
});
|
||||
let chunk_pos = wpos.0.xy().as_().wpos_to_cpos();
|
||||
data.npcs
|
||||
.character_map
|
||||
.entry(chunk_pos)
|
||||
|
@ -874,7 +874,7 @@ impl StateExt for State {
|
||||
} else {
|
||||
self.notify_players(ServerGeneral::server_msg(
|
||||
comp::ChatType::Kill(kill_source.clone(), *uid),
|
||||
msg.content().clone(),
|
||||
msg.into_content(),
|
||||
))
|
||||
}
|
||||
},
|
||||
|
@ -384,8 +384,7 @@ impl SessionState {
|
||||
.new_message(ChatType::CommandError.into_msg(match time {
|
||||
0 => Content::localized("hud-chat-goodbye"),
|
||||
_ => Content::localized_with_args("hud-chat-connection_lost", [(
|
||||
"time",
|
||||
time.to_string(),
|
||||
"time", time,
|
||||
)]),
|
||||
}));
|
||||
},
|
||||
@ -988,7 +987,7 @@ impl PlayState for SessionState {
|
||||
self.hud.new_message(ChatType::Meta.into_msg(
|
||||
Content::localized_with_args(
|
||||
"hud-trade-invite_sent",
|
||||
[("playername", name.as_str())],
|
||||
[("playername", name)],
|
||||
),
|
||||
));
|
||||
client.send_invite(uid, InviteKind::Trade)
|
||||
|
Loading…
Reference in New Issue
Block a user