NPCs now call for help when you hit them. Redraw speech bubble dark mode.

This commit is contained in:
CapsizeGlimmer 2020-05-25 20:11:22 -04:00 committed by Pfauenauge90
parent 3c07d02218
commit 3cea76b82f
24 changed files with 191 additions and 52 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -383,5 +383,8 @@ Willenskraft
"esc_menu.logout": "Ausloggen",
"esc_menu.quit_game": "Desktop",
/// End Escape Menu Section
},
vector_map: {
}
)
)

View File

@ -376,14 +376,52 @@ Fitness
Willpower
"#,
/// Start character window section
/// End character window section
/// Start Escape Menu Section
"esc_menu.logout": "Logout",
"esc_menu.quit_game": "Quit Game",
/// End Escape Menu Section
},
vector_map: {
"npc.speech.villager_under_attack": [
"Help, I'm under attack!",
"Help! I'm under attack!",
"Ouch! I'm under attack!",
"Ouch! I'm under attack! Help!",
"Help me! I'm under attack!",
"I'm under attack! Help!",
"I'm under attack! Help me!",
"Help!",
"Help! Help!",
"Help! Help! Help!",
"I'm under attack!",
"AAAHHH! I'm under attack!",
"AAAHHH! I'm under attack! Help!",
"Help! We're under attack!",
"Help! Murderer!",
"Help! There's a murder on the loose!",
"Help! They're trying to kill me!",
"Guards, I'm under attack!",
"Guards! I'm under attack!",
"I'm under attack! Guards!",
"Help! Guards! I'm under attack!",
"Guards! Come quick!",
"Guards! Guards!",
"Guards! There's a villain attacking me!",
"Guards, slay this foul villain!",
"Guards! There's a murderer!",
"Guards! Help me!",
"You won't get away with this! Guards!",
"You fiend!",
"Help me!",
"Help! Please!",
"Ouch! Guards! Help!",
"They're coming for me!",
"Help! Help! I'm being repressed",
"Ah, now we see the violence inherent in the system.",
],
}
)

View File

@ -323,5 +323,8 @@ Force
Dexterité
Intelligence"#,
},
vector_map: {
}
)
)

View File

@ -524,5 +524,8 @@ Volontà
"esc_menu.logout": "Disconnettiti",
"esc_menu.quit_game": "Esci dal Gioco",
/// End Escape Menu Section
}
},
vector_map: {
}
)

View File

@ -368,5 +368,8 @@ Força de vontade
"esc_menu.logout": "Desconectar",
"esc_menu.quit_game": "Sair do jogo",
/// End Escape Menu Section
},
vector_map: {
}
)

View File

@ -365,5 +365,8 @@ https://account.veloren.net."#,
"esc_menu.logout": "Выйти в меню",
"esc_menu.quit_game": "Выйти из игры",
/// End Escape Menu Section
},
vector_map: {
}
)

View File

@ -397,5 +397,8 @@ Hareket gücü
"esc_menu.logout": "Çıkış yap",
"esc_menu.quit_game": "Oyundan çık",
/// End Escape Menu Section
},
vector_map: {
}
)

View File

@ -89,13 +89,46 @@ impl Default for Activity {
/// Default duration in seconds of speech bubbles
pub const SPEECH_BUBBLE_DURATION: f64 = 5.0;
/// The contents of a speech bubble
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SpeechBubbleMessage {
/// This message was said by a player and needs no translation
Plain(String),
/// This message was said by an NPC. The fields are a i18n key and a random
/// u16 index
Localized(String, u16),
}
/// Adds a speech bubble to the entity
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SpeechBubble {
pub message: String,
pub message: SpeechBubbleMessage,
pub timeout: Option<Time>,
// TODO add icon enum for player chat type / npc quest+trade
}
impl Component for SpeechBubble {
type Storage = FlaggedStorage<Self, HashMapStorage<Self>>;
}
impl SpeechBubble {
pub fn npc_new(i18n_key: String, now: Time) -> Self {
let message = SpeechBubbleMessage::Localized(i18n_key, rand::random());
let timeout = Some(Time(now.0 + SPEECH_BUBBLE_DURATION));
Self { message, timeout }
}
pub fn player_new(message: String, now: Time) -> Self {
let message = SpeechBubbleMessage::Plain(message);
let timeout = Some(Time(now.0 + SPEECH_BUBBLE_DURATION));
Self { message, timeout }
}
pub fn message<F>(&self, i18n_variation: F) -> String
where
F: Fn(String, u16) -> String,
{
match &self.message {
SpeechBubbleMessage::Plain(m) => m.to_string(),
SpeechBubbleMessage::Localized(k, i) => i18n_variation(k.to_string(), *i).to_string(),
}
}
}

View File

@ -4,7 +4,7 @@ use crate::{
agent::Activity,
item::{tool::ToolKind, ItemKind},
Agent, Alignment, CharacterState, ControlAction, Controller, Loadout, MountState, Ori, Pos,
Scale, Stats,
Scale, SpeechBubble, Stats,
},
path::Chaser,
state::{DeltaTime, Time},
@ -38,6 +38,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Alignment>,
WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>,
WriteStorage<'a, SpeechBubble>,
ReadStorage<'a, MountState>,
);
@ -58,6 +59,7 @@ impl<'a> System<'a> for Sys {
alignments,
mut agents,
mut controllers,
mut speech_bubbles,
mount_states,
): Self::SystemData,
) {
@ -386,6 +388,10 @@ impl<'a> System<'a> for Sys {
if !agent.activity.is_attack() {
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
{
let message = "npc.speech.villager_under_attack".to_string();
let bubble = SpeechBubble::npc_new(message, *time);
let _ = speech_bubbles.insert(entity, bubble);
agent.activity = Activity::Attack {
target: attacker,
chaser: Chaser::default(),

View File

@ -6,7 +6,7 @@ use crate::{
use common::{
comp::{
Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, SpeechBubble,
Stats, Vel, SPEECH_BUBBLE_DURATION,
Stats, Vel,
},
event::{EventBus, ServerEvent},
msg::{
@ -76,8 +76,6 @@ impl<'a> System<'a> for Sys {
) {
timer.start();
let time = time.0;
let persistence_db_dir = &persistence_db_dir.0;
let mut server_emitter = server_event_bus.emitter();
@ -97,13 +95,13 @@ impl<'a> System<'a> for Sys {
// Update client ping.
if new_msgs.len() > 0 {
client.last_ping = time
} else if time - client.last_ping > CLIENT_TIMEOUT // Timeout
client.last_ping = time.0
} else if time.0 - client.last_ping > CLIENT_TIMEOUT // Timeout
|| client.postbox.error().is_some()
// Postbox error
{
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
} else if time - client.last_ping > CLIENT_TIMEOUT * 0.5 {
} else if time.0 - client.last_ping > CLIENT_TIMEOUT * 0.5 {
// Try pinging the client if the timeout is nearing.
client.postbox.send_message(ServerMsg::Ping);
}
@ -406,11 +404,7 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::ChatCmd(entity, argv));
continue;
} else {
let timeout = Some(Time(time + SPEECH_BUBBLE_DURATION));
let bubble = SpeechBubble {
message: message.clone(),
timeout,
};
let bubble = SpeechBubble::player_new(message.clone(), *time);
let _ = speech_bubbles.insert(entity, bubble);
match players.get(entity) {
Some(player) => {

View File

@ -21,7 +21,6 @@ impl<'a> System<'a> for Sys {
.map(|(ent, _)| ent)
.collect();
for ent in expired_ents {
println!("Remoaving bobble");
speech_bubbles.remove(ent);
}

View File

@ -954,6 +954,7 @@ impl Hud {
own_level,
&global_state.settings.gameplay,
self.pulse,
&self.voxygen_i18n,
&self.imgs,
&self.fonts,
)

View File

@ -1,5 +1,6 @@
use super::{img_ids::Imgs, HP_COLOR, LOW_HP_COLOR, MANA_COLOR};
use crate::{
i18n::VoxygenLocalization,
settings::GameplaySettings,
ui::{fonts::ConrodVoxygenFonts, Ingameable},
};
@ -51,6 +52,7 @@ pub struct Overhead<'a> {
own_level: u32,
settings: &'a GameplaySettings,
pulse: f32,
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
#[conrod(common_builder)]
@ -66,6 +68,7 @@ impl<'a> Overhead<'a> {
own_level: u32,
settings: &'a GameplaySettings,
pulse: f32,
voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
) -> Self {
@ -77,6 +80,7 @@ impl<'a> Overhead<'a> {
own_level,
settings,
pulse,
voxygen_i18n,
imgs,
fonts,
common: widget::CommonBuilder::default(),
@ -139,7 +143,11 @@ impl<'a> Widget for Overhead<'a> {
// Speech bubble
if let Some(bubble) = self.bubble {
let dark_mode = self.settings.speech_bubble_dark_mode;
let mut text = Text::new(&bubble.message)
let localizer =
|s: String, i| -> String { self.voxygen_i18n.get_variation(&s, i).to_string() };
let bubble_contents: String = bubble.message(localizer);
let mut text = Text::new(&bubble_contents)
.font_id(self.fonts.cyri.conrod_id)
.font_size(18)
.up_from(state.ids.name, 10.0)

View File

@ -53,10 +53,15 @@ pub type VoxygenFonts = HashMap<String, Font>;
pub struct VoxygenLocalization {
/// A map storing the localized texts
///
/// Localized content can be access using a String key
/// Localized content can be accessed using a String key.
pub string_map: HashMap<String, String>,
/// Either to convert the input text encoded in UTF-8
/// A map for storing variations of localized texts, for example multiple
/// ways of saying "Help, I'm under attack". Used primarily for npc
/// dialogue.
pub vector_map: HashMap<String, Vec<String>>,
/// Whether to convert the input text encoded in UTF-8
/// into a ASCII version by using the `deunicode` crate.
pub convert_utf8_to_ascii: bool,
@ -78,23 +83,56 @@ impl VoxygenLocalization {
}
}
/// Return the missing keys compared to the reference language and return
/// them
pub fn list_missing_entries(&self) -> HashSet<String> {
/// Get a variation of localized text from the given key
///
/// `index` should be a random number from `0` to `u16::max()`
///
/// If the key is not present in the localization object
/// then the key is returned.
pub fn get_variation<'a>(&'a self, key: &'a str, index: u16) -> &str {
match self.vector_map.get(key) {
Some(v) if !v.is_empty() => &v[index as usize % v.len()],
_ => key,
}
}
/// Return the missing keys compared to the reference language
pub fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) {
let reference_localization =
load_expect::<VoxygenLocalization>(i18n_asset_key(REFERENCE_LANG).as_ref());
let reference_keys: HashSet<_> =
reference_localization.string_map.keys().cloned().collect();
let current_keys: HashSet<_> = self.string_map.keys().cloned().collect();
reference_keys.difference(&current_keys).cloned().collect()
let reference_string_keys: HashSet<_> =
reference_localization.string_map.keys().cloned().collect();
let string_keys: HashSet<_> = self.string_map.keys().cloned().collect();
let strings = reference_string_keys
.difference(&string_keys)
.cloned()
.collect();
let reference_vector_keys: HashSet<_> =
reference_localization.vector_map.keys().cloned().collect();
let vector_keys: HashSet<_> = self.vector_map.keys().cloned().collect();
let vectors = reference_vector_keys
.difference(&vector_keys)
.cloned()
.collect();
(strings, vectors)
}
/// Log missing entries (compared to the reference language) as warnings
pub fn log_missing_entries(&self) {
for missing_key in self.list_missing_entries() {
let (missing_strings, missing_vectors) = self.list_missing_entries();
for missing_key in missing_strings {
log::warn!(
"[{:?}] Missing key {:?}",
"[{:?}] Missing string key {:?}",
self.metadata.language_identifier,
missing_key
);
}
for missing_key in missing_vectors {
log::warn!(
"[{:?}] Missing vector key {:?}",
self.metadata.language_identifier,
missing_key
);
@ -116,6 +154,10 @@ impl Asset for VoxygenLocalization {
for value in asked_localization.string_map.values_mut() {
*value = deunicode(value);
}
for value in asked_localization.vector_map.values_mut() {
*value = value.into_iter().map(|s| deunicode(s)).collect();
}
}
asked_localization.metadata.language_name =
deunicode(&asked_localization.metadata.language_name);