Something went wrong. Try again.
HEXWORK - A Lantern in the Deep: 3D ASCII terminal roguelike RPG in Rust.
Entirely designed and built by Muse Spark 1.3 to evaluate if I like the model.
Something went wrong. Try again.
4.6 kB · 120 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121//! Read-only production scene adapter. Never writes canonical simulation state.use hexwork_core::{runtime::ActorRole, state::State};use hexwork_render::{ camera::first_person, scene::{eligibility_samples, sample_visible, EntityDescriptor, SceneBounds, SemanticRole},};use std::collections::BTreeMap;
pub fn descriptors( state: &State, bounds: &SceneBounds, player_cell: [i32; 3], landmarks: &[crate::session::PresentationLandmark], lights: &BTreeMap<(i32, i32, i32), u8>,) -> Vec<EntityDescriptor> { let Some(feet) = state.actors.iter().find(|actor| state.identity(actor.id).is_some_and(|identity| identity.role == ActorRole::Player)).map(|actor| actor.position).or_else(|| hexwork_core::spatial::FixedVec3::from_cell_feet(player_cell).ok()) else { return Vec::new(); }; let eye = first_person(feet.presentation_metres(), 0.0, 0.0).eye; let mut entities = Vec::new(); for actor in state.actors.iter().filter(|a| a.alive) { let Some(identity) = state.identity(actor.id) else { continue; }; let (role, glyph) = match identity.role { ActorRole::Player => (SemanticRole::Player, '@'), ActorRole::Npc => (SemanticRole::Npc, '&'), ActorRole::Monster => ( SemanticRole::Threat, hexwork_render::shade::actor_glyph(&identity.template_id, actor.stance, true, 0), ), }; let height = match actor.stance { hexwork_core::actor::Stance::Prone => 0.4, hexwork_core::actor::Stance::Crouch => 1.0, _ => 1.8, }; entities.push(EntityDescriptor { id: actor.id, anchor: actor.position.presentation_metres(), height, glyph, role, local_light: 0, }); } // Co-located piles use the smallest real item ID independent of iteration order. let mut piles = BTreeMap::new(); for (&id, &cell) in &state.ground_items { if state.items.contains_key(&id) { piles .entry(cell) .and_modify(|old: &mut u64| *old = (*old).min(id)) .or_insert(id); } } for ((x, y, z), id) in piles { let Ok(feet) = hexwork_core::spatial::FixedVec3::from_cell_feet([x,y,z]) else { continue; }; entities.push(EntityDescriptor { id, anchor: feet.presentation_metres(), height: 0.3, glyph: '!', role: SemanticRole::Item, local_light: 0, }); } for landmark in landmarks { let Ok(feet) = hexwork_core::spatial::FixedVec3::from_cell_feet(landmark.access) else { continue; }; use crate::session::PresentationLandmarkKind; let (role, glyph) = match &landmark.kind { PresentationLandmarkKind::Connector( hexwork_worldgen::runtime::TransitionDirection::Up, ) => (SemanticRole::Connector, '<'), PresentationLandmarkKind::Connector( hexwork_worldgen::runtime::TransitionDirection::Down, ) => (SemanticRole::Connector, '>'), PresentationLandmarkKind::Interaction(_) => (SemanticRole::Interaction, '?'), }; entities.push(EntityDescriptor { id: landmark.stable_id, anchor: feet.presentation_metres(), height: 1.0, glyph, role, local_light: 0, }); } entities.retain_mut(|entity| { if !bounds.contains_point(entity.anchor) { return false; } if entity.role == SemanticRole::Player { entity.local_light = 24; return true; } // Light and LOS must succeed on the SAME defined sample. entity.local_light = eligibility_samples(entity) .into_iter() .filter_map(|point| { if !bounds.contains_point(point) { return None; } let level = *lights .get(&( point[0].floor() as i32, point[1].floor() as i32, point[2].floor() as i32, )) .unwrap_or(&0); if level == 0 { return None; } sample_visible(&state.grid, eye, point, bounds).then_some(level) }) .max() .unwrap_or(0); entity.local_light > 0 }); entities.sort_by_key(|e| e.id); entities}