mirror of
https://gitlab.com/veloren/veloren.git
synced 2025-07-25 21:02:31 +00:00
Merge branch 'isse/save-the-plants' into 'master'
Npcs can catch you stealing See merge request veloren/veloren!4637
This commit is contained in:
@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Wild Legoom and Goblin mobs
|
||||
- Bloodservants, Strigoi, and Scarlet Spectacles added to Halloween event
|
||||
- IP Bans
|
||||
- Npcs can catch you stealing.
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -46,6 +46,7 @@ hud-deactivate = Deactivate
|
||||
hud-collect = Collect
|
||||
hud-pick_up = Pick up
|
||||
hud-open = Open
|
||||
hud-steal = Steal
|
||||
hud-use = Use
|
||||
hud-read = Read
|
||||
hud-unlock-requires = Open with { $item }
|
||||
|
@ -298,6 +298,18 @@ npc-speech-witness_murder =
|
||||
.a0 = Murderer!
|
||||
.a1 = How could you do this?
|
||||
.a2 = Aaargh!
|
||||
npc-speech-witness_theft =
|
||||
.a0 = That's not yours!
|
||||
.a1 = Keep your hands to yourself.
|
||||
.a2 = Don't touch that!
|
||||
.a3 = Thief!
|
||||
.a4 = Give that back.
|
||||
.a5 = What do you think you're doing?
|
||||
.a6 = Stop or I'll call for the guards.
|
||||
npc-speech-witness_theft_owned =
|
||||
.a0 = Hey! That's mine.
|
||||
.a1 = Why are you touching my things?
|
||||
.a2 = You're not welcome here if you take my stuff.
|
||||
npc-speech-witness_enemy_murder =
|
||||
.a0 = My Hero!
|
||||
.a1 = Finally someone did it!
|
||||
|
@ -593,6 +593,12 @@ impl Block {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_owned(&self) -> bool {
|
||||
self.get_attr::<sprite::Owned>()
|
||||
.is_ok_and(|sprite::Owned(b)| b)
|
||||
}
|
||||
|
||||
/// The tool required to mine this block. For blocks that cannot be mined,
|
||||
/// `None` is returned.
|
||||
#[inline]
|
||||
@ -657,6 +663,16 @@ impl Block {
|
||||
#[inline]
|
||||
pub fn kind(&self) -> BlockKind { self.kind }
|
||||
|
||||
/// If possible, copy the sprite/color data of the other block.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_data_of(mut self, other: Block) -> Self {
|
||||
if self.is_filled() == other.is_filled() {
|
||||
self = self.with_data(other.data());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// If this block is a fluid, replace its sprite.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
|
@ -114,23 +114,6 @@ sprites! {
|
||||
Loom = 0x27,
|
||||
DismantlingBench = 0x28,
|
||||
RepairBench = 0x29,
|
||||
// Containers
|
||||
Chest = 0x30,
|
||||
DungeonChest0 = 0x31,
|
||||
DungeonChest1 = 0x32,
|
||||
DungeonChest2 = 0x33,
|
||||
DungeonChest3 = 0x34,
|
||||
DungeonChest4 = 0x35,
|
||||
DungeonChest5 = 0x36,
|
||||
CoralChest = 0x37,
|
||||
HaniwaUrn = 0x38,
|
||||
TerracottaChest = 0x39,
|
||||
SahaginChest = 0x3A,
|
||||
CommonLockedChest = 0x3B,
|
||||
ChestBuried = 0x3C,
|
||||
Crate = 0x3D,
|
||||
Barrel = 0x3E,
|
||||
CrateBlock = 0x3F,
|
||||
// Wall
|
||||
HangingBasket = 0x50,
|
||||
HangingSign = 0x51,
|
||||
@ -155,7 +138,7 @@ sprites! {
|
||||
Hearth = 0x72,
|
||||
},
|
||||
// Sprites representing plants that may grow over time (this does not include plant parts, like fruit).
|
||||
Plant = 3 has Growth {
|
||||
Plant = 3 has Growth, Owned {
|
||||
// Cacti
|
||||
BarrelCactus = 0x00,
|
||||
RoundCactus = 0x01,
|
||||
@ -251,7 +234,7 @@ sprites! {
|
||||
},
|
||||
// Solid resources
|
||||
// TODO: Remove small variants, make deposit size be an attribute
|
||||
Resources = 4 {
|
||||
Resource = 4 has Owned {
|
||||
// Gems and ores
|
||||
// Woods and twigs
|
||||
Twigs = 0x00,
|
||||
@ -394,6 +377,24 @@ sprites! {
|
||||
FireBowlGround = 4,
|
||||
MesaLantern = 5,
|
||||
},
|
||||
Container = 9 has Ori, Owned {
|
||||
Chest = 0x00,
|
||||
DungeonChest0 = 0x01,
|
||||
DungeonChest1 = 0x02,
|
||||
DungeonChest2 = 0x03,
|
||||
DungeonChest3 = 0x04,
|
||||
DungeonChest4 = 0x05,
|
||||
DungeonChest5 = 0x06,
|
||||
CoralChest = 0x07,
|
||||
HaniwaUrn = 0x08,
|
||||
TerracottaChest = 0x09,
|
||||
SahaginChest = 0x0A,
|
||||
CommonLockedChest = 0x0B,
|
||||
ChestBuried = 0x0C,
|
||||
Crate = 0x0D,
|
||||
Barrel = 0x0E,
|
||||
CrateBlock = 0x0F,
|
||||
},
|
||||
}
|
||||
|
||||
attributes! {
|
||||
@ -401,6 +402,7 @@ attributes! {
|
||||
Growth { bits: 4, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Growth(x)| x as u16 },
|
||||
LightEnabled { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |LightEnabled(x)| x as u16 },
|
||||
Damage { bits: 3, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Damage(x)| x as u16 },
|
||||
Owned { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |Owned(x)| x as u16 },
|
||||
}
|
||||
|
||||
// The orientation of the sprite, 0..16
|
||||
@ -419,6 +421,9 @@ impl Default for Growth {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct LightEnabled(pub bool);
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Owned(pub bool);
|
||||
|
||||
impl Default for LightEnabled {
|
||||
fn default() -> Self { Self(true) }
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
use common::{resources::TimeOfDay, rtsim::Actor};
|
||||
use common::{
|
||||
resources::TimeOfDay,
|
||||
rtsim::{Actor, SiteId},
|
||||
terrain::SpriteKind,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slotmap::HopSlotMap;
|
||||
use std::ops::Deref;
|
||||
@ -21,7 +25,7 @@ pub use common::rtsim::ReportId;
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Report {
|
||||
pub kind: ReportKind,
|
||||
pub at: TimeOfDay,
|
||||
pub at_tod: TimeOfDay,
|
||||
}
|
||||
|
||||
impl Report {
|
||||
@ -37,13 +41,25 @@ impl Report {
|
||||
DAYS * 5.0
|
||||
}
|
||||
},
|
||||
// TODO: Could consider what was stolen here
|
||||
ReportKind::Theft { .. } => DAYS * 1.5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum ReportKind {
|
||||
Death { actor: Actor, killer: Option<Actor> },
|
||||
Death {
|
||||
actor: Actor,
|
||||
killer: Option<Actor>,
|
||||
},
|
||||
Theft {
|
||||
thief: Actor,
|
||||
/// Where the theft happened.
|
||||
site: Option<SiteId>,
|
||||
/// What was stolen.
|
||||
sprite: SpriteKind,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||
@ -56,8 +72,9 @@ impl Reports {
|
||||
|
||||
pub fn cleanup(&mut self, current_time: TimeOfDay) {
|
||||
// Forget reports that are too old
|
||||
self.reports
|
||||
.retain(|_, report| (current_time.0 - report.at.0).max(0.0) < report.remember_for());
|
||||
self.reports.retain(|_, report| {
|
||||
(current_time.0 - report.at_tod.0).max(0.0) < report.remember_for()
|
||||
});
|
||||
// TODO: Limit global number of reports
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ use crate::{RtState, Rule};
|
||||
use common::{
|
||||
mounting::VolumePos,
|
||||
resources::{Time, TimeOfDay},
|
||||
rtsim::{Actor, NpcId},
|
||||
rtsim::{Actor, NpcId, SiteId},
|
||||
terrain::SpriteKind,
|
||||
};
|
||||
use vek::*;
|
||||
use world::{IndexRef, World};
|
||||
@ -38,6 +39,16 @@ pub struct OnDeath {
|
||||
}
|
||||
impl Event for OnDeath {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OnTheft {
|
||||
pub actor: Actor,
|
||||
pub wpos: Vec3<i32>,
|
||||
pub sprite: SpriteKind,
|
||||
pub site: Option<SiteId>,
|
||||
}
|
||||
|
||||
impl Event for OnTheft {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OnMountVolume {
|
||||
pub actor: Actor,
|
||||
|
@ -25,7 +25,7 @@ use common::{
|
||||
rtsim::{Actor, ChunkResource, NpcInput, PersonalityTrait, Profession, Role, SiteId},
|
||||
spiral::Spiral2d,
|
||||
store::Id,
|
||||
terrain::{CoordinateConversions, TerrainChunkSize},
|
||||
terrain::{sprite, CoordinateConversions, TerrainChunkSize},
|
||||
time::DayPeriod,
|
||||
util::Dir,
|
||||
};
|
||||
@ -1294,9 +1294,15 @@ fn check_inbox<S: State>(ctx: &mut NpcCtx) -> Option<impl Action<S>> {
|
||||
loop {
|
||||
match ctx.inbox.pop_front() {
|
||||
Some(NpcInput::Report(report_id)) if !ctx.known_reports.contains(&report_id) => {
|
||||
#[allow(clippy::single_match)]
|
||||
match ctx.state.data().reports.get(report_id).map(|r| r.kind) {
|
||||
Some(ReportKind::Death { killer, actor, .. })
|
||||
let data = ctx.state.data();
|
||||
let Some(report) = data.reports.get(report_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
const REPORT_RESPONSE_TIME: f64 = 60.0 * 5.0;
|
||||
|
||||
match report.kind {
|
||||
ReportKind::Death { killer, actor, .. }
|
||||
if matches!(&ctx.npc.role, Role::Civilised(_)) =>
|
||||
{
|
||||
// TODO: Don't report self
|
||||
@ -1334,15 +1340,53 @@ fn check_inbox<S: State>(ctx: &mut NpcCtx) -> Option<impl Action<S>> {
|
||||
"npc-speech-witness_death"
|
||||
};
|
||||
ctx.known_reports.insert(report_id);
|
||||
break Some(
|
||||
just(move |ctx, _| {
|
||||
ctx.controller.say(killer, Content::localized(phrase))
|
||||
})
|
||||
.l(),
|
||||
);
|
||||
|
||||
if ctx.time_of_day.0 - report.at_tod.0 < REPORT_RESPONSE_TIME {
|
||||
break Some(
|
||||
just(move |ctx, _| {
|
||||
ctx.controller.say(killer, Content::localized(phrase))
|
||||
})
|
||||
.l()
|
||||
.l(),
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(ReportKind::Death { .. }) => {}, // We don't care about death
|
||||
None => {}, // Stale report, ignore
|
||||
ReportKind::Theft {
|
||||
thief,
|
||||
site,
|
||||
sprite,
|
||||
} => {
|
||||
// Check if this happened at home, where we know what belongs to who
|
||||
if let Some(site) = site
|
||||
&& ctx.npc.home == Some(site)
|
||||
{
|
||||
// TODO: Don't hardcode sentiment change.
|
||||
ctx.sentiments
|
||||
.toward_mut(thief)
|
||||
.change_by(-0.2, Sentiment::VILLAIN);
|
||||
ctx.known_reports.insert(report_id);
|
||||
|
||||
let phrase = if matches!(ctx.npc.profession(), Some(Profession::Farmer))
|
||||
&& matches!(sprite.category(), sprite::Category::Plant)
|
||||
{
|
||||
"npc-speech-witness_theft_own"
|
||||
} else {
|
||||
"npc-speech-witness_theft"
|
||||
};
|
||||
|
||||
if ctx.time_of_day.0 - report.at_tod.0 < REPORT_RESPONSE_TIME {
|
||||
break Some(
|
||||
just(move |ctx, _| {
|
||||
ctx.controller.say(thief, Content::localized(phrase))
|
||||
})
|
||||
.r()
|
||||
.l(),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
// We don't care about deaths of non-civilians
|
||||
ReportKind::Death { .. } => {},
|
||||
}
|
||||
},
|
||||
Some(NpcInput::Report(_)) => {}, // Reports we already know of are ignored
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
data::{report::ReportKind, Report},
|
||||
event::{EventCtx, OnDeath},
|
||||
event::{EventCtx, OnDeath, OnTheft},
|
||||
RtState, Rule, RuleError,
|
||||
};
|
||||
use common::rtsim::NpcInput;
|
||||
@ -10,6 +10,7 @@ pub struct ReportEvents;
|
||||
impl Rule for ReportEvents {
|
||||
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||
rtstate.bind::<Self, OnDeath>(on_death);
|
||||
rtstate.bind::<Self, OnTheft>(on_theft);
|
||||
|
||||
Ok(Self)
|
||||
}
|
||||
@ -31,7 +32,7 @@ fn on_death(ctx: EventCtx<ReportEvents, OnDeath>) {
|
||||
actor: ctx.event.actor,
|
||||
killer: ctx.event.killer,
|
||||
},
|
||||
at: data.time_of_day,
|
||||
at_tod: data.time_of_day,
|
||||
});
|
||||
|
||||
// TODO: Don't push report to NPC inboxes, have a dedicated data structure that
|
||||
@ -45,3 +46,30 @@ fn on_death(ctx: EventCtx<ReportEvents, OnDeath>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_theft(ctx: EventCtx<ReportEvents, OnTheft>) {
|
||||
let data = &mut *ctx.state.data_mut();
|
||||
|
||||
let nearby = data
|
||||
.npcs
|
||||
.nearby(None, ctx.event.wpos.as_(), 24.0)
|
||||
.filter_map(|actor| actor.npc())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !nearby.is_empty() {
|
||||
let report = data.reports.create(Report {
|
||||
kind: ReportKind::Theft {
|
||||
thief: ctx.event.actor,
|
||||
site: ctx.event.site,
|
||||
sprite: ctx.event.sprite,
|
||||
},
|
||||
at_tod: data.time_of_day,
|
||||
});
|
||||
|
||||
for npc_id in nearby {
|
||||
if let Some(npc) = data.npcs.get_mut(npc_id) {
|
||||
npc.inbox.push_back(NpcInput::Report(report));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use hashbrown::HashSet;
|
||||
use rand::{seq::IteratorRandom, Rng};
|
||||
use specs::{
|
||||
join::Join, shred, DispatcherBuilder, Entities, Entity as EcsEntity, Read, ReadExpect,
|
||||
ReadStorage, SystemData, Write, WriteStorage,
|
||||
ReadStorage, SystemData, Write, WriteExpect, WriteStorage,
|
||||
};
|
||||
use tracing::{debug, error, warn};
|
||||
use vek::{Rgb, Vec3};
|
||||
@ -14,7 +14,7 @@ use common::{
|
||||
item::{self, flatten_counted_items, tool::AbilityMap, MaterialStatManifest},
|
||||
loot_owner::LootOwnerKind,
|
||||
slot::{self, Slot},
|
||||
InventoryUpdate, LootOwner, PickupItem,
|
||||
InventoryUpdate, LootOwner, PickupItem, PresenceKind,
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
event::{
|
||||
@ -77,9 +77,12 @@ pub struct InventoryManipData<'a> {
|
||||
events: Events<'a>,
|
||||
block_change: Write<'a, common_state::BlockChange>,
|
||||
trades: Write<'a, Trades>,
|
||||
rtsim: WriteExpect<'a, crate::rtsim::RtSim>,
|
||||
terrain: ReadExpect<'a, common::terrain::TerrainGrid>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
time: Read<'a, Time>,
|
||||
world: ReadExpect<'a, std::sync::Arc<world::World>>,
|
||||
index: ReadExpect<'a, world::IndexOwned>,
|
||||
program_time: ReadExpect<'a, ProgramTime>,
|
||||
ability_map: ReadExpect<'a, AbilityMap>,
|
||||
msm: ReadExpect<'a, MaterialStatManifest>,
|
||||
@ -107,6 +110,7 @@ pub struct InventoryManipData<'a> {
|
||||
pets: ReadStorage<'a, comp::Pet>,
|
||||
velocities: ReadStorage<'a, comp::Vel>,
|
||||
masses: ReadStorage<'a, comp::Mass>,
|
||||
presences: ReadStorage<'a, comp::Presence>,
|
||||
}
|
||||
|
||||
impl ServerEvent for InventoryManipEvent {
|
||||
@ -351,6 +355,20 @@ impl ServerEvent for InventoryManipEvent {
|
||||
|
||||
if let Some(block) = block {
|
||||
if block.is_collectible() && data.block_change.can_set_block(sprite_pos) {
|
||||
if block.is_owned()
|
||||
&& let Some(PresenceKind::Character(character)) =
|
||||
data.presences.get(entity).map(|p| p.kind)
|
||||
{
|
||||
data.rtsim.hook_pickup_owned_sprite(
|
||||
&data.world,
|
||||
data.index.as_index_ref(),
|
||||
block
|
||||
.get_sprite()
|
||||
.expect("If the block is owned, it is a sprite"),
|
||||
sprite_pos,
|
||||
common::rtsim::Actor::Character(character),
|
||||
);
|
||||
}
|
||||
// If an item was required to collect the sprite, consume it now
|
||||
if let Some((inv_slot, true)) = required_item {
|
||||
inventory.take(inv_slot, &data.ability_map, &data.msm);
|
||||
|
@ -7,6 +7,7 @@ use common::{
|
||||
grid::Grid,
|
||||
mounting::VolumePos,
|
||||
rtsim::{Actor, ChunkResource, NpcId, RtSimEntity, WorldSettings},
|
||||
terrain::{CoordinateConversions, SpriteKind},
|
||||
};
|
||||
use common_ecs::{dispatch, System};
|
||||
use common_state::BlockDiff;
|
||||
@ -14,7 +15,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use enum_map::EnumMap;
|
||||
use rtsim::{
|
||||
data::{npc::SimulationMode, Data, ReadError},
|
||||
event::{OnDeath, OnMountVolume, OnSetup},
|
||||
event::{OnDeath, OnMountVolume, OnSetup, OnTheft},
|
||||
RtState,
|
||||
};
|
||||
use specs::DispatcherBuilder;
|
||||
@ -157,6 +158,33 @@ impl RtSim {
|
||||
self.state.emit(OnMountVolume { actor, pos }, world, index)
|
||||
}
|
||||
|
||||
pub fn hook_pickup_owned_sprite(
|
||||
&mut self,
|
||||
world: &World,
|
||||
index: IndexRef,
|
||||
sprite: SpriteKind,
|
||||
wpos: Vec3<i32>,
|
||||
actor: Actor,
|
||||
) {
|
||||
let site = world.sim().get(wpos.xy().wpos_to_cpos()).and_then(|chunk| {
|
||||
chunk
|
||||
.sites
|
||||
.iter()
|
||||
.find_map(|site| self.state.data().sites.world_site_map.get(site).copied())
|
||||
});
|
||||
|
||||
self.state.emit(
|
||||
OnTheft {
|
||||
actor,
|
||||
wpos,
|
||||
sprite,
|
||||
site,
|
||||
},
|
||||
world,
|
||||
index,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) {
|
||||
if let Some(chunk_state) = self.state.get_resource_mut::<ChunkStates>().0.get_mut(key) {
|
||||
*chunk_state = Some(LoadedChunkState { max_res });
|
||||
|
@ -2046,6 +2046,7 @@ impl Hud {
|
||||
vec![(
|
||||
Some(GameInput::Interact),
|
||||
i18n.get_msg("hud-pick_up").to_string(),
|
||||
overitem::TEXT_COLOR,
|
||||
)],
|
||||
)
|
||||
.set(overitem_id, ui_widgets);
|
||||
@ -2077,17 +2078,19 @@ impl Hud {
|
||||
let pos = mat.mul_point(Vec3::broadcast(0.5));
|
||||
let over_pos = pos + Vec3::unit_z() * 0.7;
|
||||
|
||||
let interaction_text = |collect_default| match interaction {
|
||||
let interaction_text = |collect_default, color| match interaction {
|
||||
BlockInteraction::Collect => {
|
||||
vec![(
|
||||
Some(GameInput::Interact),
|
||||
i18n.get_msg(collect_default).to_string(),
|
||||
color,
|
||||
)]
|
||||
},
|
||||
BlockInteraction::Craft(_) => {
|
||||
vec![(
|
||||
Some(GameInput::Interact),
|
||||
i18n.get_msg("hud-use").to_string(),
|
||||
color,
|
||||
)]
|
||||
},
|
||||
BlockInteraction::Unlock(kind) => {
|
||||
@ -2103,19 +2106,23 @@ impl Hud {
|
||||
.unwrap_or_else(|| "modular item".to_string())
|
||||
};
|
||||
|
||||
vec![(Some(GameInput::Interact), match kind {
|
||||
UnlockKind::Free => i18n.get_msg("hud-open").to_string(),
|
||||
UnlockKind::Requires(item_id) => i18n
|
||||
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
||||
"item" => item_name(item_id),
|
||||
})
|
||||
.to_string(),
|
||||
UnlockKind::Consumes(item_id) => i18n
|
||||
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
||||
"item" => item_name(item_id),
|
||||
})
|
||||
.to_string(),
|
||||
})]
|
||||
vec![(
|
||||
Some(GameInput::Interact),
|
||||
match kind {
|
||||
UnlockKind::Free => i18n.get_msg("hud-open").to_string(),
|
||||
UnlockKind::Requires(item_id) => i18n
|
||||
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
||||
"item" => item_name(item_id),
|
||||
})
|
||||
.to_string(),
|
||||
UnlockKind::Consumes(item_id) => i18n
|
||||
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
|
||||
"item" => item_name(item_id),
|
||||
})
|
||||
.to_string(),
|
||||
},
|
||||
color,
|
||||
)]
|
||||
},
|
||||
BlockInteraction::Mine(mine_tool) => {
|
||||
match (mine_tool, &info.active_mine_tool) {
|
||||
@ -2123,24 +2130,35 @@ impl Hud {
|
||||
vec![(
|
||||
Some(GameInput::Primary),
|
||||
i18n.get_msg("hud-mine").to_string(),
|
||||
color,
|
||||
)]
|
||||
},
|
||||
(ToolKind::Pick, _) => {
|
||||
vec![(None, i18n.get_msg("hud-mine-needs_pickaxe").to_string())]
|
||||
vec![(
|
||||
None,
|
||||
i18n.get_msg("hud-mine-needs_pickaxe").to_string(),
|
||||
color,
|
||||
)]
|
||||
},
|
||||
(ToolKind::Shovel, Some(ToolKind::Shovel)) => {
|
||||
vec![(
|
||||
Some(GameInput::Primary),
|
||||
i18n.get_msg("hud-dig").to_string(),
|
||||
color,
|
||||
)]
|
||||
},
|
||||
(ToolKind::Shovel, _) => {
|
||||
vec![(None, i18n.get_msg("hud-mine-needs_shovel").to_string())]
|
||||
vec![(
|
||||
None,
|
||||
i18n.get_msg("hud-mine-needs_shovel").to_string(),
|
||||
color,
|
||||
)]
|
||||
},
|
||||
_ => {
|
||||
vec![(
|
||||
None,
|
||||
i18n.get_msg("hud-mine-needs_unhandled_case").to_string(),
|
||||
color,
|
||||
)]
|
||||
},
|
||||
}
|
||||
@ -2156,11 +2174,12 @@ impl Hud {
|
||||
) => "hud-lay",
|
||||
_ => "hud-sit",
|
||||
};
|
||||
vec![(Some(GameInput::Mount), i18n.get_msg(key).to_string())]
|
||||
vec![(Some(GameInput::Mount), i18n.get_msg(key).to_string(), color)]
|
||||
},
|
||||
BlockInteraction::Read(_) => vec![(
|
||||
Some(GameInput::Interact),
|
||||
i18n.get_msg("hud-read").to_string(),
|
||||
color,
|
||||
)],
|
||||
// TODO: change to turn on/turn off?
|
||||
BlockInteraction::LightToggle(enable) => vec![(
|
||||
@ -2171,6 +2190,7 @@ impl Hud {
|
||||
"hud-deactivate"
|
||||
})
|
||||
.to_string(),
|
||||
color,
|
||||
)],
|
||||
};
|
||||
|
||||
@ -2180,6 +2200,12 @@ impl Hud {
|
||||
.filter(|s| s.is_container())
|
||||
.and_then(|s| get_sprite_desc(s, i18n))
|
||||
{
|
||||
let (text, color) = if block.is_owned() {
|
||||
("hud-steal", overitem::NEGATIVE_TEXT_COLOR)
|
||||
} else {
|
||||
("hud-open", overitem::TEXT_COLOR)
|
||||
};
|
||||
|
||||
overitem::Overitem::new(
|
||||
desc,
|
||||
overitem::TEXT_COLOR,
|
||||
@ -2190,7 +2216,7 @@ impl Hud {
|
||||
overitem_properties,
|
||||
self.pulse,
|
||||
&global_state.window.key_layout,
|
||||
interaction_text("hud-open"),
|
||||
interaction_text(text, color),
|
||||
)
|
||||
.x_y(0.0, 100.0)
|
||||
.position_ingame(over_pos)
|
||||
@ -2204,6 +2230,11 @@ impl Hud {
|
||||
.flatten()
|
||||
.next()
|
||||
{
|
||||
let (text, color) = if block.is_owned() {
|
||||
("hud-steal", overitem::NEGATIVE_TEXT_COLOR)
|
||||
} else {
|
||||
("hud-collect", overitem::TEXT_COLOR)
|
||||
};
|
||||
item.set_amount(amount.clamp(1, item.max_amount()))
|
||||
.expect("amount >= 1 and <= max_amount is always a valid amount");
|
||||
make_overitem(
|
||||
@ -2212,11 +2243,16 @@ impl Hud {
|
||||
pos.distance_squared(player_pos),
|
||||
overitem_properties,
|
||||
&self.fonts,
|
||||
interaction_text("hud-collect"),
|
||||
interaction_text(text, color),
|
||||
)
|
||||
.set(overitem_id, ui_widgets);
|
||||
} else if let Some(desc) = block.get_sprite().and_then(|s| get_sprite_desc(s, i18n))
|
||||
{
|
||||
let (text, color) = if block.is_owned() {
|
||||
("hud-steal", overitem::NEGATIVE_TEXT_COLOR)
|
||||
} else {
|
||||
("hud-collect", overitem::TEXT_COLOR)
|
||||
};
|
||||
overitem::Overitem::new(
|
||||
desc,
|
||||
overitem::TEXT_COLOR,
|
||||
@ -2227,7 +2263,7 @@ impl Hud {
|
||||
overitem_properties,
|
||||
self.pulse,
|
||||
&global_state.window.key_layout,
|
||||
interaction_text("hud-collect"),
|
||||
interaction_text(text, color),
|
||||
)
|
||||
.x_y(0.0, 100.0)
|
||||
.position_ingame(over_pos)
|
||||
@ -2285,6 +2321,7 @@ impl Hud {
|
||||
"hud-use"
|
||||
})
|
||||
.to_string(),
|
||||
overitem::TEXT_COLOR,
|
||||
)],
|
||||
)
|
||||
.x_y(0.0, 100.0)
|
||||
|
@ -15,6 +15,7 @@ use crate::hud::{CollectFailedData, HudCollectFailedReason, HudLootOwner};
|
||||
use keyboard_keynames::key_layout::KeyLayout;
|
||||
|
||||
pub const TEXT_COLOR: Color = Color::Rgba(0.61, 0.61, 0.89, 1.0);
|
||||
pub const NEGATIVE_TEXT_COLOR: Color = Color::Rgba(0.91, 0.15, 0.17, 1.0);
|
||||
pub const PICKUP_FAILED_FADE_OUT_TIME: f32 = 1.5;
|
||||
|
||||
widget_ids! {
|
||||
@ -24,7 +25,7 @@ widget_ids! {
|
||||
name,
|
||||
// Interaction hints
|
||||
btn_bg,
|
||||
btn,
|
||||
btns[],
|
||||
// Inventory full
|
||||
inv_full_bg,
|
||||
inv_full,
|
||||
@ -47,7 +48,7 @@ pub struct Overitem<'a> {
|
||||
pulse: f32,
|
||||
key_layout: &'a Option<KeyLayout>,
|
||||
// GameInput optional so we can just show stuff like "needs pickaxe"
|
||||
interaction_options: Vec<(Option<GameInput>, String)>,
|
||||
interaction_options: Vec<(Option<GameInput>, String, Color)>,
|
||||
}
|
||||
|
||||
impl<'a> Overitem<'a> {
|
||||
@ -61,7 +62,7 @@ impl<'a> Overitem<'a> {
|
||||
properties: OveritemProperties,
|
||||
pulse: f32,
|
||||
key_layout: &'a Option<KeyLayout>,
|
||||
interaction_options: Vec<(Option<GameInput>, String)>,
|
||||
interaction_options: Vec<(Option<GameInput>, String, Color)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
@ -174,42 +175,56 @@ impl<'a> Widget for Overitem<'a> {
|
||||
|
||||
// Interaction hints
|
||||
if !self.interaction_options.is_empty() && self.properties.active {
|
||||
let text = self
|
||||
let texts = self
|
||||
.interaction_options
|
||||
.iter()
|
||||
.filter_map(|(input, action)| {
|
||||
.filter_map(|(input, action, color)| {
|
||||
let binding = if let Some(input) = input {
|
||||
Some(self.controls.get_binding(*input)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some((binding, action))
|
||||
Some((binding, action, color))
|
||||
})
|
||||
.map(|(input, action)| {
|
||||
.map(|(input, action, color)| {
|
||||
if let Some(input) = input {
|
||||
let input = input.display_string(self.key_layout);
|
||||
format!("{} {action}", input.as_str())
|
||||
(format!("{} {action}", input.as_str()), color)
|
||||
} else {
|
||||
action.to_string()
|
||||
(action.to_string(), color)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
.collect::<Vec<_>>();
|
||||
if state.ids.btns.len() < texts.len() {
|
||||
state.update(|state| {
|
||||
state
|
||||
.ids
|
||||
.btns
|
||||
.resize(texts.len(), &mut ui.widget_id_generator());
|
||||
})
|
||||
}
|
||||
|
||||
let hints_text = Text::new(&text)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(btn_font_size as u32)
|
||||
.color(TEXT_COLOR)
|
||||
.x_y(0.0, btn_text_pos_y)
|
||||
.depth(self.distance_from_player_sqr + 1.0)
|
||||
.parent(id);
|
||||
let mut max_w = btn_rect_size;
|
||||
let mut max_h = 0.0;
|
||||
|
||||
let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
|
||||
for (idx, (text, color)) in texts.iter().enumerate() {
|
||||
let hints_text = Text::new(text)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(btn_font_size as u32)
|
||||
.color(**color)
|
||||
.x_y(0.0, btn_text_pos_y + max_h)
|
||||
.depth(self.distance_from_player_sqr + 1.0)
|
||||
.parent(id);
|
||||
let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
|
||||
max_w = max_w.max(w);
|
||||
max_h += h;
|
||||
hints_text.set(state.ids.btns[idx], ui);
|
||||
}
|
||||
|
||||
hints_text.set(state.ids.btn, ui);
|
||||
max_h = max_h.max(btn_rect_size);
|
||||
|
||||
RoundedRectangle::fill_with(
|
||||
[w + btn_radius * 2.0, h + btn_radius * 2.0],
|
||||
[max_w + btn_radius * 2.0, max_h + btn_radius * 2.0],
|
||||
btn_radius,
|
||||
btn_color,
|
||||
)
|
||||
|
@ -233,10 +233,10 @@ impl Primitive {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Fill {
|
||||
Sprite(SpriteKind),
|
||||
RotatedSprite(SpriteKind, u8),
|
||||
RotatedSpriteWithCfg(SpriteKind, u8, SpriteCfg),
|
||||
ResourceSprite(SpriteKind, u8),
|
||||
Sprite(Block),
|
||||
ResourceSprite(Block),
|
||||
CfgSprite(Block, SpriteCfg),
|
||||
|
||||
Block(Block),
|
||||
Brick(BlockKind, Rgb<u8>, u8),
|
||||
Gradient(util::gradient::Gradient, BlockKind),
|
||||
@ -248,6 +248,44 @@ pub enum Fill {
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
pub fn sprite(kind: SpriteKind) -> Self { Fill::Block(Block::empty().with_sprite(kind)) }
|
||||
|
||||
pub fn sprite_ori(kind: SpriteKind, ori: u8) -> Self {
|
||||
let block = Block::empty().with_sprite(kind);
|
||||
|
||||
let block = block.with_ori(ori).unwrap_or(block);
|
||||
Fill::Sprite(block)
|
||||
}
|
||||
|
||||
pub fn resource_sprite(kind: SpriteKind) -> Self {
|
||||
Fill::ResourceSprite(Block::empty().with_sprite(kind))
|
||||
}
|
||||
|
||||
pub fn resource_sprite_ori(kind: SpriteKind, ori: u8) -> Self {
|
||||
let block = Block::empty().with_sprite(kind);
|
||||
|
||||
let block = block.with_ori(ori).unwrap_or(block);
|
||||
Fill::ResourceSprite(block)
|
||||
}
|
||||
|
||||
pub fn owned_resource_sprite_ori(kind: SpriteKind, ori: u8) -> Self {
|
||||
let block = Block::empty().with_sprite(kind);
|
||||
|
||||
let block = block.with_ori(ori).unwrap_or(block);
|
||||
let block = block
|
||||
.with_attr(common::terrain::sprite::Owned(true))
|
||||
.unwrap_or(block);
|
||||
|
||||
Fill::ResourceSprite(block)
|
||||
}
|
||||
|
||||
pub fn sprite_ori_cfg(kind: SpriteKind, ori: u8, cfg: SpriteCfg) -> Self {
|
||||
let block = Block::empty().with_sprite(kind);
|
||||
|
||||
let block = block.with_ori(ori).unwrap_or(block);
|
||||
Fill::CfgSprite(block, cfg)
|
||||
}
|
||||
|
||||
fn contains_at(
|
||||
tree: &Store<Primitive>,
|
||||
prim: Id<Primitive>,
|
||||
@ -480,37 +518,22 @@ impl Fill {
|
||||
) -> Option<Block> {
|
||||
if Self::contains_at(tree, prim, pos, col) {
|
||||
match self {
|
||||
Fill::Block(block) => Some(*block),
|
||||
Fill::Sprite(sprite) => Some(if old_block.is_filled() {
|
||||
Block::air(*sprite)
|
||||
} else {
|
||||
old_block.with_sprite(*sprite)
|
||||
}),
|
||||
Fill::RotatedSprite(sprite, ori) | Fill::ResourceSprite(sprite, ori) => {
|
||||
Fill::Sprite(sprite) | Fill::ResourceSprite(sprite) => {
|
||||
Some(if old_block.is_filled() {
|
||||
Block::air(*sprite)
|
||||
.with_ori(*ori)
|
||||
.unwrap_or_else(|| Block::air(*sprite))
|
||||
*sprite
|
||||
} else {
|
||||
old_block
|
||||
.with_sprite(*sprite)
|
||||
.with_ori(*ori)
|
||||
.unwrap_or_else(|| old_block.with_sprite(*sprite))
|
||||
old_block.with_data_of(*sprite)
|
||||
})
|
||||
},
|
||||
Fill::RotatedSpriteWithCfg(sprite, ori, cfg) => Some({
|
||||
Fill::CfgSprite(sprite, cfg) => {
|
||||
*sprite_cfg = Some(cfg.clone());
|
||||
if old_block.is_filled() {
|
||||
Block::air(*sprite)
|
||||
.with_ori(*ori)
|
||||
.unwrap_or_else(|| Block::air(*sprite))
|
||||
Some(if old_block.is_filled() {
|
||||
*sprite
|
||||
} else {
|
||||
old_block
|
||||
.with_sprite(*sprite)
|
||||
.with_ori(*ori)
|
||||
.unwrap_or_else(|| old_block.with_sprite(*sprite))
|
||||
}
|
||||
}),
|
||||
old_block.with_data_of(*sprite)
|
||||
})
|
||||
},
|
||||
Fill::Block(block) => Some(*block),
|
||||
Fill::Brick(bk, col, range) => Some(Block::new(
|
||||
*bk,
|
||||
*col + (RandomField::new(13)
|
||||
@ -1080,7 +1103,7 @@ impl Painter {
|
||||
min: pos,
|
||||
max: pos + 1,
|
||||
})
|
||||
.fill(Fill::Sprite(sprite))
|
||||
.fill(Fill::sprite(sprite))
|
||||
}
|
||||
|
||||
/// Places a sprite at the provided location with the provided orientation.
|
||||
@ -1089,7 +1112,7 @@ impl Painter {
|
||||
min: pos,
|
||||
max: pos + 1,
|
||||
})
|
||||
.fill(Fill::RotatedSprite(sprite, ori))
|
||||
.fill(Fill::sprite_ori(sprite, ori))
|
||||
}
|
||||
|
||||
/// Places a sprite at the provided location with the provided orientation
|
||||
@ -1105,7 +1128,7 @@ impl Painter {
|
||||
min: pos,
|
||||
max: pos + 1,
|
||||
})
|
||||
.fill(Fill::RotatedSpriteWithCfg(sprite, ori, cfg))
|
||||
.fill(Fill::sprite_ori_cfg(sprite, ori, cfg))
|
||||
}
|
||||
|
||||
/// Places a sprite at the provided location with the provided orientation
|
||||
@ -1116,7 +1139,18 @@ impl Painter {
|
||||
min: pos,
|
||||
max: pos + 1,
|
||||
})
|
||||
.fill(Fill::ResourceSprite(sprite, ori))
|
||||
.fill(Fill::resource_sprite_ori(sprite, ori))
|
||||
}
|
||||
|
||||
/// Places a sprite at the provided location with the provided orientation
|
||||
/// which will be tracked by rtsim nature if the sprite has an associated
|
||||
/// [`ChunkResource`].
|
||||
pub fn owned_resource_sprite(&self, pos: Vec3<i32>, sprite: SpriteKind, ori: u8) {
|
||||
self.aabb(Aabb {
|
||||
min: pos,
|
||||
max: pos + 1,
|
||||
})
|
||||
.fill(Fill::owned_resource_sprite_ori(sprite, ori))
|
||||
}
|
||||
|
||||
/// Returns a `PrimitiveRef` of the largest pyramid with a slope of 1 that
|
||||
|
@ -671,7 +671,7 @@ fn render_tower(bridge: &Bridge, painter: &Painter, roof_kind: &RoofKind) {
|
||||
1,
|
||||
4,
|
||||
)
|
||||
.fill(Fill::Sprite(SpriteKind::FireBowlGround));
|
||||
.fill(Fill::sprite(SpriteKind::FireBowlGround));
|
||||
},
|
||||
RoofKind::Hipped => {
|
||||
painter
|
||||
@ -829,7 +829,7 @@ fn render_hang(bridge: &Bridge, painter: &Painter) {
|
||||
|
||||
edges
|
||||
.translate(Vec3::unit_z())
|
||||
.fill(Fill::Sprite(SpriteKind::Rope));
|
||||
.fill(Fill::sprite(SpriteKind::Rope));
|
||||
|
||||
edges.translate(Vec3::unit_z() * 2).fill(wood);
|
||||
|
||||
|
@ -220,7 +220,7 @@ impl Structure for CliffTower {
|
||||
max: (sprite_pos + 1).with_z(floor_level + 2),
|
||||
})
|
||||
.clear();
|
||||
painter.sprite(
|
||||
painter.owned_resource_sprite(
|
||||
sprite_pos.with_z(floor_level + 1),
|
||||
match (RandomField::new(0).get(sprite_pos.with_z(floor_level + 1)))
|
||||
% 8
|
||||
@ -231,6 +231,7 @@ impl Structure for CliffTower {
|
||||
3 => SpriteKind::Pot,
|
||||
_ => SpriteKind::MesaLantern,
|
||||
},
|
||||
0,
|
||||
);
|
||||
}
|
||||
// planters
|
||||
@ -504,7 +505,7 @@ impl Structure for CliffTower {
|
||||
// distribute small sprites
|
||||
for dir in LOCALITY {
|
||||
let pos = plot_center + dir * ((length / 3) - 1);
|
||||
painter.sprite(
|
||||
painter.owned_resource_sprite(
|
||||
pos.with_z(floor_level + 1),
|
||||
match (RandomField::new(0).get(pos.with_z(floor_level)))
|
||||
% 9
|
||||
@ -519,6 +520,7 @@ impl Structure for CliffTower {
|
||||
7 => SpriteKind::Bowl,
|
||||
_ => SpriteKind::MesaLantern,
|
||||
},
|
||||
0,
|
||||
);
|
||||
}
|
||||
// beds & wardrobes
|
||||
@ -605,7 +607,7 @@ impl Structure for CliffTower {
|
||||
SpriteKind::WallTableMesa,
|
||||
(4 * d) as u8,
|
||||
);
|
||||
painter.rotated_sprite(
|
||||
painter.owned_resource_sprite(
|
||||
pos.with_z(floor_level + 4),
|
||||
match (RandomField::new(0).get(pos.with_z(floor_level)))
|
||||
% 3
|
||||
@ -620,7 +622,7 @@ impl Structure for CliffTower {
|
||||
// distribute small sprites
|
||||
for dir in LOCALITY {
|
||||
let pos = plot_center + dir * ((length / 3) + 1);
|
||||
painter.sprite(
|
||||
painter.owned_resource_sprite(
|
||||
pos.with_z(floor_level + 1),
|
||||
match (RandomField::new(0).get(pos.with_z(floor_level)))
|
||||
% 12
|
||||
@ -638,6 +640,7 @@ impl Structure for CliffTower {
|
||||
10 => SpriteKind::MesaLantern,
|
||||
_ => SpriteKind::FountainArabic,
|
||||
},
|
||||
0,
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -666,7 +669,7 @@ impl Structure for CliffTower {
|
||||
SpriteKind::WallTableMesa,
|
||||
(4 * d) as u8,
|
||||
);
|
||||
painter.rotated_sprite(
|
||||
painter.owned_resource_sprite(
|
||||
pos.with_z(floor_level + 3),
|
||||
match (RandomField::new(0).get(pos.with_z(floor_level)))
|
||||
% 4
|
||||
@ -682,7 +685,7 @@ impl Structure for CliffTower {
|
||||
// distribute small sprites
|
||||
for dir in LOCALITY {
|
||||
let pos = plot_center + dir * ((length / 3) + 1);
|
||||
painter.sprite(
|
||||
painter.owned_resource_sprite(
|
||||
pos.with_z(floor_level + 1),
|
||||
match (RandomField::new(0).get(pos.with_z(floor_level)))
|
||||
% 11
|
||||
@ -700,6 +703,7 @@ impl Structure for CliffTower {
|
||||
10 => SpriteKind::JugAndBowlArabic,
|
||||
_ => SpriteKind::OvenArabic,
|
||||
},
|
||||
0,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -364,7 +364,11 @@ impl Structure for CoastalHouse {
|
||||
let sprite = sprites.swap_remove(
|
||||
RandomField::new(0).get(position.with_z(base)) as usize % sprites.len(),
|
||||
);
|
||||
painter.sprite(position.with_z(base - 2 + (s * height)), sprite);
|
||||
painter.owned_resource_sprite(
|
||||
position.with_z(base - 2 + (s * height)),
|
||||
sprite,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,7 +489,11 @@ impl Structure for DesertCityMultiPlot {
|
||||
0 => {
|
||||
for dir in NEIGHBORS {
|
||||
let pos = center + dir * 4;
|
||||
painter.sprite(pos.with_z(floor_level), SpriteKind::Crate);
|
||||
painter.owned_resource_sprite(
|
||||
pos.with_z(floor_level),
|
||||
SpriteKind::Crate,
|
||||
0,
|
||||
);
|
||||
}
|
||||
for dir in NEIGHBORS {
|
||||
let pos = center + dir * 8;
|
||||
@ -1222,7 +1226,7 @@ impl Structure for DesertCityMultiPlot {
|
||||
SpriteKind::WallTableArabic,
|
||||
6 - (4 * d) as u8,
|
||||
);
|
||||
painter.rotated_sprite(
|
||||
painter.owned_resource_sprite(
|
||||
c_pos.with_z(floor_level + 1),
|
||||
match (RandomField::new(0)
|
||||
.get(c_pos.with_z(floor_level)))
|
||||
@ -1317,7 +1321,7 @@ impl Structure for DesertCityMultiPlot {
|
||||
SpriteKind::WallTableArabic,
|
||||
6 - (4 * d) as u8,
|
||||
);
|
||||
painter.rotated_sprite(
|
||||
painter.owned_resource_sprite(
|
||||
a_pos.with_z(floor_level + 1),
|
||||
match (RandomField::new(0)
|
||||
.get(a_pos.with_z(floor_level)))
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::{ColumnSample, Land};
|
||||
use common::terrain::{Block, BlockKind, SpriteKind};
|
||||
use common::terrain::{sprite::Owned, Block, BlockKind, SpriteKind};
|
||||
use rand::prelude::*;
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use vek::*;
|
||||
@ -253,7 +253,12 @@ impl Structure for FarmField {
|
||||
.sprites()
|
||||
.choose_weighted(rng, |(w, _)| *w)
|
||||
.ok()
|
||||
.and_then(|&(_, s)| Some(old.into_vacant().with_sprite(s?)))
|
||||
.and_then(|&(_, s)| {
|
||||
let new = old.into_vacant().with_sprite(s?);
|
||||
let new = new.with_attr(Owned(true)).unwrap_or(new);
|
||||
|
||||
Some(new)
|
||||
})
|
||||
} else if z_off == 1 && rng.gen_bool(0.001) {
|
||||
Some(old.into_vacant().with_sprite(SpriteKind::Scarecrow))
|
||||
} else {
|
||||
|
@ -9,6 +9,7 @@ use common::{
|
||||
terrain::{Block, BlockKind, SpriteKind},
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use strum::IntoEnumIterator;
|
||||
use vek::*;
|
||||
|
||||
/// Represents house data generated by the `generate()` method
|
||||
@ -1564,14 +1565,19 @@ impl Structure for House {
|
||||
// drawer next to bed
|
||||
painter.sprite(nightstand_pos.with_z(base), SpriteKind::DrawerSmall);
|
||||
// collectible on top of drawer
|
||||
let rng = RandomField::new(0).get(nightstand_pos.with_z(base + 1));
|
||||
painter.sprite(nightstand_pos.with_z(base + 1), match rng % 5 {
|
||||
0 => SpriteKind::Lantern,
|
||||
1 => SpriteKind::PotionMinor,
|
||||
2 => SpriteKind::VialEmpty,
|
||||
3 => SpriteKind::Bowl,
|
||||
_ => SpriteKind::Empty,
|
||||
});
|
||||
let rng0 = RandomField::new(0).get(nightstand_pos.with_z(base + 1));
|
||||
let rng1 = RandomField::new(1).get(nightstand_pos.with_z(base + 1));
|
||||
painter.owned_resource_sprite(
|
||||
nightstand_pos.with_z(base + 1),
|
||||
match rng0 % 5 {
|
||||
0 => SpriteKind::Lantern,
|
||||
1 => SpriteKind::PotionMinor,
|
||||
2 => SpriteKind::VialEmpty,
|
||||
3 => SpriteKind::Bowl,
|
||||
_ => SpriteKind::Empty,
|
||||
},
|
||||
(rng1 % 4) as u8 * 2,
|
||||
);
|
||||
// wardrobe along wall in corner of the room
|
||||
let (wardrobe_pos, drawer_ori) = match self.front {
|
||||
Dir::Y => (Vec2::new(self.bounds.max.x - 2, self.bounds.min.y + 1), 4),
|
||||
@ -1589,14 +1595,19 @@ impl Structure for House {
|
||||
for dir in DIRS {
|
||||
// random accent pieces and loot
|
||||
let sprite_pos = self.bounds.center() + dir * 5;
|
||||
let rng = RandomField::new(0).get(sprite_pos.with_z(base));
|
||||
painter.sprite(sprite_pos.with_z(base), match rng % 32 {
|
||||
0..=2 => SpriteKind::Crate,
|
||||
3..=4 => SpriteKind::CoatRack,
|
||||
5..=7 => SpriteKind::Pot,
|
||||
8..=9 => SpriteKind::Lantern,
|
||||
_ => SpriteKind::Empty,
|
||||
});
|
||||
let rng0 = RandomField::new(0).get(sprite_pos.with_z(base));
|
||||
let rng1 = RandomField::new(1).get(sprite_pos.with_z(base));
|
||||
painter.owned_resource_sprite(
|
||||
sprite_pos.with_z(base),
|
||||
match rng0 % 32 {
|
||||
0..=2 => SpriteKind::Crate,
|
||||
3..=4 => SpriteKind::CoatRack,
|
||||
5..=7 => SpriteKind::Pot,
|
||||
8..=9 => SpriteKind::Lantern,
|
||||
_ => SpriteKind::Empty,
|
||||
},
|
||||
(rng1 % 4) as u8 * 2,
|
||||
);
|
||||
}
|
||||
|
||||
if self.bounds.max.x - self.bounds.min.x < 16
|
||||
@ -1605,12 +1616,12 @@ impl Structure for House {
|
||||
let table_pos = Vec2::new(half_x, half_y);
|
||||
// room is smaller, so use small table
|
||||
painter.sprite(table_pos.with_z(base), SpriteKind::TableDining);
|
||||
for (idx, dir) in CARDINALS.iter().enumerate() {
|
||||
let chair_pos = table_pos + dir;
|
||||
for dir in Dir::iter() {
|
||||
let chair_pos = table_pos + dir.to_vec2();
|
||||
painter.rotated_sprite(
|
||||
chair_pos.with_z(base),
|
||||
SpriteKind::ChairSingle,
|
||||
(idx * 2 + ((idx % 2) * 4)) as u8,
|
||||
dir.opposite().sprite_ori(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@ -1621,12 +1632,12 @@ impl Structure for House {
|
||||
_ => Vec2::new(quarter_x, half_y),
|
||||
};
|
||||
painter.sprite(table_pos.with_z(base), SpriteKind::TableDouble);
|
||||
for (idx, dir) in CARDINALS.iter().enumerate() {
|
||||
let chair_pos = table_pos + dir * (1 + idx % 2) as i32;
|
||||
for dir in Dir::iter() {
|
||||
let chair_pos = table_pos + dir.select((2, 1)) * dir.to_vec2();
|
||||
painter.rotated_sprite(
|
||||
chair_pos.with_z(base),
|
||||
SpriteKind::ChairSingle,
|
||||
(idx * 2 + ((idx % 2) * 4)) as u8,
|
||||
dir.opposite().sprite_ori(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
util::{RandomField, Sampler, CARDINALS, DIAGONALS},
|
||||
Land,
|
||||
};
|
||||
use common::terrain::{BlockKind, SpriteKind};
|
||||
use common::terrain::{sprite::Owned, BlockKind, SpriteKind};
|
||||
use rand::prelude::*;
|
||||
use std::{f32::consts::TAU, sync::Arc};
|
||||
use vek::*;
|
||||
@ -50,11 +50,15 @@ impl Structure for SavannahHut {
|
||||
let center = self.bounds.center();
|
||||
let sprite_fill = Fill::Sampling(Arc::new(|wpos| {
|
||||
Some(match (RandomField::new(0).get(wpos)) % 25 {
|
||||
0 => Block::air(SpriteKind::Bowl),
|
||||
1 => Block::air(SpriteKind::VialEmpty),
|
||||
0 => Block::air(SpriteKind::Bowl).with_attr(Owned(true)).unwrap(),
|
||||
1 => Block::air(SpriteKind::VialEmpty)
|
||||
.with_attr(Owned(true))
|
||||
.unwrap(),
|
||||
2 => Block::air(SpriteKind::Lantern),
|
||||
3 => Block::air(SpriteKind::JugArabic),
|
||||
4 => Block::air(SpriteKind::Crate),
|
||||
4 => Block::air(SpriteKind::Crate)
|
||||
.with_attr(Owned(true))
|
||||
.unwrap(),
|
||||
_ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
|
||||
})
|
||||
}));
|
||||
@ -244,7 +248,7 @@ impl Structure for SavannahHut {
|
||||
let sprite = sprites.swap_remove(
|
||||
RandomField::new(0).get(position.with_z(base)) as usize % sprites.len(),
|
||||
);
|
||||
painter.sprite(position.with_z(base - 2), sprite);
|
||||
painter.owned_resource_sprite(position.with_z(base - 2), sprite, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
generation::{EntityInfo, SpecialEntity},
|
||||
terrain::{BlockKind, SpriteKind},
|
||||
terrain::{sprite::Owned, BlockKind, SpriteKind},
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use std::sync::Arc;
|
||||
@ -43,11 +43,15 @@ impl Structure for SavannahPit {
|
||||
let center = self.bounds.center();
|
||||
let sprite_fill = Fill::Sampling(Arc::new(|wpos| {
|
||||
Some(match (RandomField::new(0).get(wpos)) % 50 {
|
||||
0 => Block::air(SpriteKind::Bowl),
|
||||
1 => Block::air(SpriteKind::VialEmpty),
|
||||
0 => Block::air(SpriteKind::Bowl).with_attr(Owned(true)).unwrap(),
|
||||
1 => Block::air(SpriteKind::VialEmpty)
|
||||
.with_attr(Owned(true))
|
||||
.unwrap(),
|
||||
2 => Block::air(SpriteKind::Lantern),
|
||||
3 => Block::air(SpriteKind::JugArabic),
|
||||
4 => Block::air(SpriteKind::Crate),
|
||||
4 => Block::air(SpriteKind::Crate)
|
||||
.with_attr(Owned(true))
|
||||
.unwrap(),
|
||||
_ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
|
||||
})
|
||||
}));
|
||||
@ -727,7 +731,11 @@ impl Structure for SavannahPit {
|
||||
RandomField::new(0).get(position.with_z(base)) as usize
|
||||
% sprites.len(),
|
||||
);
|
||||
painter.sprite(position.with_z(base - ((1 + f) * length)), sprite);
|
||||
painter.owned_resource_sprite(
|
||||
position.with_z(base - ((1 + f) * length)),
|
||||
sprite,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
generation::SpecialEntity,
|
||||
terrain::{BlockKind, SpriteKind},
|
||||
terrain::{sprite::Owned, BlockKind, SpriteKind},
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use std::{f32::consts::TAU, sync::Arc};
|
||||
@ -53,11 +53,15 @@ impl Structure for SavannahWorkshop {
|
||||
let center = self.bounds.center();
|
||||
let sprite_fill = Fill::Sampling(Arc::new(|wpos| {
|
||||
Some(match (RandomField::new(0).get(wpos)) % 25 {
|
||||
0 => Block::air(SpriteKind::Bowl),
|
||||
1 => Block::air(SpriteKind::VialEmpty),
|
||||
0 => Block::air(SpriteKind::Bowl).with_attr(Owned(true)).unwrap(),
|
||||
1 => Block::air(SpriteKind::VialEmpty)
|
||||
.with_attr(Owned(true))
|
||||
.unwrap(),
|
||||
2 => Block::air(SpriteKind::Lantern),
|
||||
3 => Block::air(SpriteKind::JugArabic),
|
||||
4 => Block::air(SpriteKind::Crate),
|
||||
4 => Block::air(SpriteKind::Crate)
|
||||
.with_attr(Owned(true))
|
||||
.unwrap(),
|
||||
_ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
|
||||
})
|
||||
}));
|
||||
|
@ -1491,10 +1491,7 @@ impl Structure for Tavern {
|
||||
max: (wall_center + in_dir.rotated_cw().to_vec2())
|
||||
.with_z(wall.base_alt + 2),
|
||||
}))
|
||||
.fill(Fill::RotatedSprite(
|
||||
SpriteKind::Window1,
|
||||
in_dir.sprite_ori(),
|
||||
));
|
||||
.fill(Fill::sprite_ori(SpriteKind::Window1, in_dir.sprite_ori()));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user