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.logout": "Ausloggen",
"esc_menu.quit_game": "Desktop", "esc_menu.quit_game": "Desktop",
/// End Escape Menu Section /// End Escape Menu Section
},
vector_map: {
} }
) )

View File

@ -376,14 +376,52 @@ Fitness
Willpower Willpower
"#, "#,
/// End character window section
/// Start character window section
/// Start Escape Menu Section /// Start Escape Menu Section
"esc_menu.logout": "Logout", "esc_menu.logout": "Logout",
"esc_menu.quit_game": "Quit Game", "esc_menu.quit_game": "Quit Game",
/// End Escape Menu Section /// 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é Dexterité
Intelligence"#, Intelligence"#,
},
vector_map: {
} }
) )

View File

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

View File

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

View File

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

View File

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

View File

@ -89,13 +89,46 @@ impl Default for Activity {
/// Default duration in seconds of speech bubbles /// Default duration in seconds of speech bubbles
pub const SPEECH_BUBBLE_DURATION: f64 = 5.0; 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 /// Adds a speech bubble to the entity
#[derive(Clone, Default, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SpeechBubble { pub struct SpeechBubble {
pub message: String, pub message: SpeechBubbleMessage,
pub timeout: Option<Time>, pub timeout: Option<Time>,
// TODO add icon enum for player chat type / npc quest+trade // TODO add icon enum for player chat type / npc quest+trade
} }
impl Component for SpeechBubble { impl Component for SpeechBubble {
type Storage = FlaggedStorage<Self, HashMapStorage<Self>>; 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, agent::Activity,
item::{tool::ToolKind, ItemKind}, item::{tool::ToolKind, ItemKind},
Agent, Alignment, CharacterState, ControlAction, Controller, Loadout, MountState, Ori, Pos, Agent, Alignment, CharacterState, ControlAction, Controller, Loadout, MountState, Ori, Pos,
Scale, Stats, Scale, SpeechBubble, Stats,
}, },
path::Chaser, path::Chaser,
state::{DeltaTime, Time}, state::{DeltaTime, Time},
@ -38,6 +38,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Alignment>, ReadStorage<'a, Alignment>,
WriteStorage<'a, Agent>, WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
WriteStorage<'a, SpeechBubble>,
ReadStorage<'a, MountState>, ReadStorage<'a, MountState>,
); );
@ -58,6 +59,7 @@ impl<'a> System<'a> for Sys {
alignments, alignments,
mut agents, mut agents,
mut controllers, mut controllers,
mut speech_bubbles,
mount_states, mount_states,
): Self::SystemData, ): Self::SystemData,
) { ) {
@ -386,6 +388,10 @@ impl<'a> System<'a> for Sys {
if !agent.activity.is_attack() { if !agent.activity.is_attack() {
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id()) 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 { agent.activity = Activity::Attack {
target: attacker, target: attacker,
chaser: Chaser::default(), chaser: Chaser::default(),

View File

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

View File

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

View File

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

View File

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

View File

@ -53,10 +53,15 @@ pub type VoxygenFonts = HashMap<String, Font>;
pub struct VoxygenLocalization { pub struct VoxygenLocalization {
/// A map storing the localized texts /// 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>, 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. /// into a ASCII version by using the `deunicode` crate.
pub convert_utf8_to_ascii: bool, pub convert_utf8_to_ascii: bool,
@ -78,23 +83,56 @@ impl VoxygenLocalization {
} }
} }
/// Return the missing keys compared to the reference language and return /// Get a variation of localized text from the given key
/// them ///
pub fn list_missing_entries(&self) -> HashSet<String> { /// `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 = let reference_localization =
load_expect::<VoxygenLocalization>(i18n_asset_key(REFERENCE_LANG).as_ref()); 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 /// Log missing entries (compared to the reference language) as warnings
pub fn log_missing_entries(&self) { 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!( 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, self.metadata.language_identifier,
missing_key missing_key
); );
@ -116,6 +154,10 @@ impl Asset for VoxygenLocalization {
for value in asked_localization.string_map.values_mut() { for value in asked_localization.string_map.values_mut() {
*value = deunicode(value); *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 = asked_localization.metadata.language_name =
deunicode(&asked_localization.metadata.language_name); deunicode(&asked_localization.metadata.language_name);