An engine for roguelikes. Intended to make seven-hour roguelikes easier. Not finished.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

253 lines
6.5 KiB

pub mod player;
pub mod ai;
use super::*;
use crate::world::{inventory::Inventory, item::{Item, ItemRef, Slot}};
use rogue_util::{coord::*, raster, grid::Grid};
use std::fmt::Debug;
use rand::Rng;
pub trait Visible {
fn is_transparent(&self) -> bool;
}
impl Visible for Tile {
fn is_transparent(&self) -> bool {
match self.content {
Content::Air | Content::Window => true,
Content::Stone | Content::Fog => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LinfDir {
UL, U, UR,
L, R,
DL, D, DR,
}
impl LinfDir {
pub fn to_v2i(self) -> V2i {
use LinfDir::*;
match self {
UL => V2i(-1, -1),
U => V2i(0, -1),
UR => V2i(1, -1),
L => V2i(-1, 0),
R => V2i(1, 0),
DL => V2i(-1, 1),
D => V2i(0, 1),
DR => V2i(1, 1),
}
}
pub fn from_v2i(v: V2i) -> LinfDir {
use LinfDir::*;
use std::cmp::Ordering::*;
match (v.0.cmp(&0), v.1.cmp(&0)) {
(Less, Less) => UL,
(Equal, Less) => U,
(Greater, Less) => UR,
(Less, Equal) => L,
(Greater, Equal) => R,
(Less, Greater) => DL,
(Equal, Greater) => D,
(Greater, Greater) => DR,
_ => R, // panic? Option?
}
}
}
pub type Hp = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DamageKind {
Phys, Magic,
#[doc(hidden)]
_NeverExhaustive
}
#[derive(Debug, Clone, Copy)]
pub struct DamageDesc {
pub kind: DamageKind,
pub base: Hp,
pub add: Hp,
}
#[derive(Debug, Clone, Copy)]
pub struct Damage {
pub kind: DamageKind,
pub amount: Hp,
}
impl DamageDesc {
pub fn roll(&self) -> Damage {
let mut amt = self.base;
if self.add > 0 {
amt += rand::thread_rng().gen_range(0 ..= self.add);
}
Damage { kind: self.kind, amount: amt }
}
}
impl From<&DamageDesc> for Damage {
fn from(dd: &DamageDesc) -> Self {
dd.roll()
}
}
#[derive(Debug, Clone)]
pub enum Action {
Move(LinfDir),
Attack(EntWeak, Rc<Vec<DamageDesc>>),
Equip(ItemRef),
Wait,
Quit,
}
impl Default for Action {
fn default() -> Action { Action::Wait }
}
#[derive(Debug, Clone)]
pub struct Observation<'w> {
pub sight: SightObs,
pub me: EntRef,
pub world: &'w World,
}
impl<'w> Observation<'w> {
pub fn new(world: &'w World, me: EntRef, v: V2i, sr: usize) -> Observation<'w> {
Observation {
sight: SightObs::new(world, v, sr),
me,
world,
}
}
}
#[derive(Debug, Clone)]
pub struct SightObs {
pub tiles: Grid<Option<V2i>>, // NB: Relative coordinate; 0,0 is this
pub entities: Vec<EntWeak>,
}
impl SightObs {
pub fn new(world: &World, v: V2i, sr: usize) -> SightObs {
let srsq = (sr as Vi) * (sr as Vi);
SightObs {
tiles: Grid::from_generator(|p| {
if p.l2_sq() > srsq {
return None;
}
{
let mut rg = world.region_mut();
for pt in raster::line(v, v + p) {
let tile = rg.get_or_create(pt);
if !tile.is_transparent() {
return None;
}
}
}
Some(v + p)
}, V2i(-(sr as Vi), -(sr as Vi)), V2i(2*sr as Vi, 2*sr as Vi)).unwrap(),
entities: world.entities().filter_map(|eref| {
if let Ok(e) = eref.try_borrow() {
let pos = e.pos();
if (pos - v).l2_sq() > srsq {
return None;
}
{
let mut rg = world.region_mut();
for pt in raster::line(v, pos) {
let tile = rg.get_or_create(pt);
if !tile.is_transparent() {
return None;
}
}
}
Some(Rc::downgrade(eref))
} else { None }
}).collect(),
}
}
}
pub const DEFAULT_SIGHT_RADIUS: usize = 10;
pub const DEFAULT_ACT_TIME: usize = 10000;
pub const DEFAULT_DAMAGE: Hp = 6;
pub const ECLS_LIVING: usize = 1 << 0;
pub const ECLS_PROTAG: usize = 1 << 1;
pub fn expected_damage(atks: Rc<Vec<DamageDesc>>) -> Hp {
atks.iter().map(|a| a.base + a.add / 2).sum()
}
pub trait Entity: Debug {
fn as_entity(&self) -> &Entity;
fn pos(&self) -> V2i;
fn set_pos(&mut self, v: V2i);
fn next_time(&self) -> usize;
fn set_next_time(&mut self, t: usize);
fn hp(&self) -> Hp;
fn set_hp(&mut self, h: Hp);
fn inv(&self) -> &Inventory;
fn inv_mut(&mut self) -> &mut Inventory;
fn class(&self) -> usize { 0 }
fn char(&self) -> char { 'E' }
fn sight(&self) -> usize { DEFAULT_SIGHT_RADIUS }
fn act_time(&self) -> usize { DEFAULT_ACT_TIME }
fn base_damage(&self) -> Rc<Vec<DamageDesc>> {
Rc::new(vec![DamageDesc {
kind: DamageKind::Phys,
base: DEFAULT_DAMAGE,
add: DEFAULT_DAMAGE,
}])
}
fn best_damage(&self) -> Rc<Vec<DamageDesc>> {
let mut bestatk = self.base_damage();
let mut bestdmg = expected_damage(Rc::clone(&bestatk));
for slot in Slot::DAMAGING.iter().cloned() {
if let Some(item) = self.inv().in_slot(slot) {
if let Some(atk) = item.borrow().damage(self.as_entity()) {
let dmg = expected_damage(Rc::clone(&atk));
if dmg > bestdmg {
bestdmg = dmg;
bestatk = atk;
}
}
}
}
bestatk
}
fn act<'w>(&mut self, _obs: &Observation<'w>) -> Action { Action::Wait }
fn heal(&mut self, amount: Hp) {
let hp = self.hp().saturating_add(amount);
self.set_hp(hp);
}
fn harm(&mut self, amount: Hp) {
let hp = self.hp().saturating_sub(amount);
self.set_hp(hp);
}
fn is_dead(&self) -> bool { self.hp() == 0 }
fn take_damage(&mut self, dmg: Damage) {
self.harm(dmg.amount);
}
}
pub type EntCell = RefCell<dyn Entity>;
pub type EntRef = Rc<EntCell>;
pub type EntWeak = Weak<EntCell>;
pub fn ent_ref<E: Entity + 'static>(entity: E) -> EntRef {
Rc::new(RefCell::new(entity))
}