mirror of
				https://github.com/osmarks/ewo3.git
				synced 2025-10-31 15:33:00 +00:00 
			
		
		
		
	useful items, multitile positions, etc
This commit is contained in:
		
							
								
								
									
										17
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -130,6 +130,12 @@ dependencies = [ | ||||
|  "crypto-common", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "equivalent" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" | ||||
|  | ||||
| [[package]] | ||||
| name = "euclid" | ||||
| version = "0.22.10" | ||||
| @@ -149,6 +155,7 @@ dependencies = [ | ||||
|  "fastrand", | ||||
|  "futures-util", | ||||
|  "hecs", | ||||
|  "indexmap", | ||||
|  "lazy_static", | ||||
|  "noise-functions", | ||||
|  "seahash", | ||||
| @@ -286,6 +293,16 @@ version = "1.8.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" | ||||
|  | ||||
| [[package]] | ||||
| name = "indexmap" | ||||
| version = "2.2.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" | ||||
| dependencies = [ | ||||
|  "equivalent", | ||||
|  "hashbrown", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "itoa" | ||||
| version = "1.0.11" | ||||
|   | ||||
| @@ -20,3 +20,4 @@ slab = "0.4" | ||||
| lazy_static = "1" | ||||
| seahash = "4" | ||||
| noise-functions = "0.2" | ||||
| indexmap = "2" | ||||
| @@ -78,6 +78,11 @@ | ||||
|         {#if health} | ||||
|             Your health is {health}. | ||||
|         {/if} | ||||
|         <ul> | ||||
|             {#each inventory as item} | ||||
|                 <li>{item[0]} x{item[2]}: {item[1]}</li> | ||||
|             {/each} | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| @@ -89,6 +94,7 @@ | ||||
|     let dead = false | ||||
|     let health | ||||
|     let players | ||||
|     let inventory = [] | ||||
|  | ||||
|     let ws | ||||
|     const connect = () => { | ||||
| @@ -105,6 +111,7 @@ | ||||
|                 } | ||||
|                 grid = newGrid | ||||
|                 health = data.Display.health | ||||
|                 inventory = data.Display.inventory | ||||
|             } | ||||
|             if (data === "Dead") { | ||||
|                 dead = true | ||||
| @@ -164,7 +171,8 @@ | ||||
|         "a": "Left", | ||||
|         "d": "Right", | ||||
|         "z": "DownLeft", | ||||
|         "x": "DownRight" | ||||
|         "x": "DownRight", | ||||
|         "f": "Dig" | ||||
|     } | ||||
|  | ||||
|     connect() | ||||
|   | ||||
							
								
								
									
										311
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										311
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,12 +1,13 @@ | ||||
| use hecs::{Entity, World}; | ||||
| use hecs::{CommandBuffer, Entity, World}; | ||||
| use euclid::{Point3D, Point2D, Vector2D}; | ||||
| use futures_util::{stream::TryStreamExt, SinkExt, StreamExt}; | ||||
| use indexmap::IndexMap; | ||||
| use noise_functions::Sample3; | ||||
| use tokio::net::{TcpListener, TcpStream}; | ||||
| use tokio_tungstenite::tungstenite::protocol::Message; | ||||
| use tokio::sync::{mpsc, Mutex}; | ||||
| use anyhow::{Result, Context, anyhow}; | ||||
| use std::{collections::{hash_map::Entry, HashMap}, hash::{Hash, Hasher}, net::SocketAddr, sync::Arc, thread::current, time::Duration}; | ||||
| use std::{collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, convert::TryFrom, hash::{Hash, Hasher}, net::SocketAddr, sync::Arc, time::Duration}; | ||||
| use slab::Slab; | ||||
| use serde::{Serialize, Deserialize}; | ||||
|  | ||||
| @@ -90,8 +91,9 @@ enum Input { | ||||
| #[derive(Serialize, Deserialize, Clone)] | ||||
| enum Frame { | ||||
|     Dead, | ||||
|     Display { nearby: Vec<(i64, i64, char, f32)>, health: f32 }, | ||||
|     PlayerCount(usize) | ||||
|     Display { nearby: Vec<(i64, i64, char, f32)>, health: f32, inventory: Vec<(String, String, u64)> }, | ||||
|     PlayerCount(usize), | ||||
|     Message(String) | ||||
| } | ||||
|  | ||||
| struct Client { | ||||
| @@ -106,16 +108,45 @@ struct GameState { | ||||
|     ticks: u64 | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||||
| enum Item { | ||||
|     Dirt, | ||||
|     Bone | ||||
| } | ||||
|  | ||||
| impl Item { | ||||
|     fn name(&self) -> &'static str { | ||||
|         use Item::*; | ||||
|         match self { | ||||
|             Dirt => "Dirt", | ||||
|             Bone => "Bone" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn description(&self) -> &'static str { | ||||
|         use Item::*; | ||||
|         match self { | ||||
|             Dirt => "It's from the ground. You're carrying it for some reason.", | ||||
|             Bone => "Disassembling your enemies for resources is probably ethical." | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct PlayerCharacter; | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||||
| struct Position(Coord); | ||||
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||||
| struct Position(VecDeque<Coord>); | ||||
|  | ||||
| impl Position { | ||||
|     fn head(&self) -> Coord { | ||||
|         *self.0.front().unwrap() | ||||
|     } | ||||
|  | ||||
|     fn single_tile(c: Coord) -> Self { | ||||
|         Self(VecDeque::from([c])) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||||
| struct MovingInto(Coord); | ||||
| @@ -123,6 +154,13 @@ struct MovingInto(Coord); | ||||
| #[derive(Debug, Clone)] | ||||
| struct Health(f32, f32); | ||||
|  | ||||
| impl Health { | ||||
|     fn pct(&self) -> f32 { | ||||
|         if self.1 == 0.0 { 0.0 } | ||||
|         else { self.0 / self.1 } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct Render(char); | ||||
|  | ||||
| @@ -153,20 +191,17 @@ struct Collidable; | ||||
| #[derive(Debug, Clone)] | ||||
| struct Velocity(CoordVec); | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct DeferredRandomly<T: Clone + std::fmt::Debug + hecs::Bundle>(u64, T); | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct Terrain; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct Obstruction { entry_cost: StochasticNumber, exit_cost: StochasticNumber } | ||||
| struct Obstruction { entry_multiplier: f32, exit_multiplier: f32 } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct Energy { current: f32, regeneration_rate: f32, burst: f32 } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct DespawnOnImpact; | ||||
| struct Drops(Vec<(Item, StochasticNumber)>); | ||||
|  | ||||
| impl Energy { | ||||
|     fn try_consume(&mut self, cost: f32) -> bool { | ||||
| @@ -179,6 +214,46 @@ impl Energy { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct DespawnOnImpact; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct Inventory(indexmap::IndexMap<Item, u64>); | ||||
|  | ||||
| impl Inventory { | ||||
|     fn add(&mut self, item: Item, qty: u64) { | ||||
|         *self.0.entry(item).or_default() += qty; | ||||
|     } | ||||
|  | ||||
|     fn take(&mut self, item: Item, qty: u64) -> bool { | ||||
|         match self.0.entry(item) { | ||||
|             indexmap::map::Entry::Occupied(mut o) => { | ||||
|                 let current = o.get_mut(); | ||||
|                 if *current >= qty { | ||||
|                     *current -= qty; | ||||
|                     return true; | ||||
|                 } | ||||
|                 return false; | ||||
|             }, | ||||
|             indexmap::map::Entry::Vacant(_) => return false | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn extend(&mut self, other: &Inventory) { | ||||
|         for (item, count) in other.0.iter() { | ||||
|             self.add(item.clone(), *count); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn is_empty(&self) -> bool { | ||||
|         self.0.iter().any(|(_, c)| *c > 0) | ||||
|     } | ||||
|  | ||||
|     fn empty() -> Self { | ||||
|         Self(IndexMap::new()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| const VIEW: i64 = 15; | ||||
| const WALL: i64 = 128; | ||||
| const RANDOM_DESPAWN_INV_RATE: u64 = 4000; | ||||
| @@ -239,21 +314,22 @@ struct EnemySpec { | ||||
|     initial_health: f32, | ||||
|     move_delay: usize, | ||||
|     attack_cooldown: u64, | ||||
|     ranged: bool | ||||
|     ranged: bool, | ||||
|     drops: Vec<(Item, StochasticNumber)> | ||||
| } | ||||
|  | ||||
| impl EnemySpec { | ||||
|     // Numbers ported from original EWO. Fudge constants added elsewhere.  | ||||
|     fn random() -> EnemySpec { | ||||
|         match fastrand::usize(0..650) { | ||||
|             0..=99 => EnemySpec { symbol: 'I', min_damage: 10.0, damage_range: 5.0, initial_health: 50.0, move_delay: 70, attack_cooldown: 10, ranged: false }, // IBIS | ||||
|             100..=199 => EnemySpec { symbol: 'K', min_damage: 5.0, damage_range: 15.0, initial_health: 30.0, move_delay: 40, attack_cooldown: 10, ranged: false }, // KESTREL | ||||
|             200..=299 => EnemySpec { symbol: 'S', min_damage: 5.0, damage_range: 5.0, initial_health: 20.0, move_delay: 50, attack_cooldown: 10, ranged: false }, // SNAKE | ||||
|             300..=399 => EnemySpec { symbol: 'E', min_damage: 10.0, damage_range: 20.0, initial_health: 80.0, move_delay: 80, attack_cooldown: 10, ranged: false }, // EMU | ||||
|             400..=499 => EnemySpec { symbol: 'O', min_damage: 8.0, damage_range: 17.0, initial_health: 150.0, move_delay: 100, attack_cooldown: 10, ranged: false }, // OGRE | ||||
|             500..=599 => EnemySpec { symbol: 'R', min_damage: 5.0, damage_range: 5.0, initial_health: 15.0, move_delay: 40, attack_cooldown: 10, ranged: false }, // RAT | ||||
|             600..=609 => EnemySpec { symbol: 'M' , min_damage: 20.0, damage_range: 10.0, initial_health: 150.0, move_delay: 70, attack_cooldown: 10, ranged: false }, // MOA | ||||
|             610..=649 => EnemySpec { symbol: 'P', min_damage: 10.0, damage_range: 5.0, initial_health: 15.0, move_delay: 20, attack_cooldown: 10, ranged: true }, // PLATYPUS | ||||
|             0..=99 => EnemySpec { symbol: 'I', min_damage: 10.0, damage_range: 5.0, initial_health: 50.0, move_delay: 70, attack_cooldown: 10, ranged: false, drops: vec![] }, // IBIS | ||||
|             100..=199 => EnemySpec { symbol: 'K', min_damage: 5.0, damage_range: 15.0, initial_health: 30.0, move_delay: 40, attack_cooldown: 10, ranged: false, drops: vec![] }, // KESTREL | ||||
|             200..=299 => EnemySpec { symbol: 'S', min_damage: 5.0, damage_range: 5.0, initial_health: 20.0, move_delay: 50, attack_cooldown: 10, ranged: false, drops: vec![] }, // SNAKE | ||||
|             300..=399 => EnemySpec { symbol: 'E', min_damage: 10.0, damage_range: 20.0, initial_health: 80.0, move_delay: 80, attack_cooldown: 10, ranged: false, drops: vec![] }, // EMU | ||||
|             400..=499 => EnemySpec { symbol: 'O', min_damage: 8.0, damage_range: 17.0, initial_health: 150.0, move_delay: 100, attack_cooldown: 10, ranged: false, drops: vec![] }, // OGRE | ||||
|             500..=599 => EnemySpec { symbol: 'R', min_damage: 5.0, damage_range: 5.0, initial_health: 15.0, move_delay: 40, attack_cooldown: 10, ranged: false, drops: vec![] }, // RAT | ||||
|             600..=609 => EnemySpec { symbol: 'M' , min_damage: 20.0, damage_range: 10.0, initial_health: 150.0, move_delay: 70, attack_cooldown: 10, ranged: false, drops: vec![] }, // MOA | ||||
|             610..=649 => EnemySpec { symbol: 'P', min_damage: 10.0, damage_range: 5.0, initial_health: 15.0, move_delay: 20, attack_cooldown: 10, ranged: true, drops: vec![] }, // PLATYPUS | ||||
|             _ => unreachable!() | ||||
|         } | ||||
|     } | ||||
| @@ -308,6 +384,10 @@ impl StochasticNumber { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn sample_rounded<T: TryFrom<i128>>(&self) -> T { | ||||
|         T::try_from(self.sample().round() as i128).map_err(|_| "convert fail").unwrap() | ||||
|     } | ||||
|  | ||||
|     fn triangle_from_min_range(min: f32, range: f32) -> Self { | ||||
|         StochasticNumber::Triangle { min: min, max: min + range, mode: (min + range) / 2.0 } | ||||
|     } | ||||
| @@ -318,41 +398,48 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|     let mut positions = HashMap::new(); | ||||
|  | ||||
|     for (entity, pos) in state.world.query_mut::<hecs::With<&Position, &Collidable>>() { | ||||
|         positions.insert(pos.0, entity); | ||||
|         for subpos in pos.0.iter() { | ||||
|             positions.insert(*subpos, entity); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     for (entity, pos) in state.world.query_mut::<hecs::With<&Position, &Terrain>>() { | ||||
|         terrain_positions.insert(pos.0, entity); | ||||
|         for subpos in pos.0.iter() { | ||||
|             terrain_positions.insert(*subpos, entity); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let mut buffer = hecs::CommandBuffer::new(); | ||||
|  | ||||
|     // Spawn enemies | ||||
|     for (_entity, (Position(pos), EnemyTarget { spawn_range, spawn_density, spawn_rate_inv, .. })) in state.world.query::<(&Position, &EnemyTarget)>().iter() { | ||||
|     for (_entity, (pos, EnemyTarget { spawn_range, spawn_density, spawn_rate_inv, .. })) in state.world.query::<(&Position, &EnemyTarget)>().iter() { | ||||
|         let pos = pos.head(); | ||||
|         if fastrand::usize(0..*spawn_rate_inv) == 0 { | ||||
|             let c = count_hexes(*spawn_range.end()); | ||||
|             let mut newpos = *pos + sample_range(*spawn_range.end()); | ||||
|             let mut newpos = pos + sample_range(*spawn_range.end()); | ||||
|             let mut occupied = false; | ||||
|             for _ in 0..(c as f32 / spawn_density * 0.005).ceil() as usize { | ||||
|                 if positions.contains_key(&newpos) { | ||||
|                     occupied = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 newpos = *pos + sample_range(*spawn_range.end()); | ||||
|                 newpos = pos + sample_range(*spawn_range.end()); | ||||
|             } | ||||
|             if !occupied && get_base_terrain(newpos).can_enter() && hex_distance(newpos, *pos) >= *spawn_range.start() { | ||||
|                 let spec = EnemySpec::random(); | ||||
|             if !occupied && get_base_terrain(newpos).can_enter() && hex_distance(newpos, pos) >= *spawn_range.start() { | ||||
|                 let mut spec = EnemySpec::random(); | ||||
|                 spec.drops.push((Item::Bone, StochasticNumber::Triangle { min: 0.7 * spec.initial_health / 40.0, max: 1.3 * spec.initial_health / 40.0, mode: spec.initial_health / 40.0 })); | ||||
|                 if spec.ranged { | ||||
|                     buffer.spawn(( | ||||
|                         Render(spec.symbol), | ||||
|                         Health(spec.initial_health, spec.initial_health), | ||||
|                         Enemy, | ||||
|                         RangedAttack { damage: StochasticNumber::triangle_from_min_range(spec.min_damage, spec.damage_range), energy: spec.attack_cooldown as f32, range: 4 }, | ||||
|                         Position(newpos), | ||||
|                         Position::single_tile(newpos), | ||||
|                         MoveCost(StochasticNumber::Triangle { min: 0.0, max: 2.0 * spec.move_delay as f32 / 3.0, mode: spec.move_delay as f32 / 3.0 }), | ||||
|                         Collidable, | ||||
|                         DespawnRandomly(RANDOM_DESPAWN_INV_RATE), | ||||
|                         Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 } | ||||
|                         Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 }, | ||||
|                         Drops(spec.drops) | ||||
|                     )); | ||||
|                 } else { | ||||
|                     buffer.spawn(( | ||||
| @@ -360,11 +447,12 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|                         Health(spec.initial_health, spec.initial_health), | ||||
|                         Enemy, | ||||
|                         Attack { damage: StochasticNumber::triangle_from_min_range(spec.min_damage, spec.damage_range), energy: spec.attack_cooldown as f32 }, | ||||
|                         Position(newpos), | ||||
|                         Position::single_tile(newpos), | ||||
|                         MoveCost(StochasticNumber::Triangle { min: 0.0, max: 2.0 * spec.move_delay as f32 / 3.0, mode: spec.move_delay as f32 / 3.0 }), | ||||
|                         Collidable, | ||||
|                         DespawnRandomly(RANDOM_DESPAWN_INV_RATE), | ||||
|                         Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 } | ||||
|                         Energy { regeneration_rate: 1.0, current: 0.0, burst: 0.0 }, | ||||
|                         Drops(spec.drops) | ||||
|                     )); | ||||
|                 } | ||||
|             } | ||||
| @@ -372,11 +460,13 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|     } | ||||
|  | ||||
|     // Process enemy motion and ranged attacks | ||||
|     for (entity, (Position(pos), ranged, energy)) in state.world.query::<hecs::With<(&Position, Option<&mut RangedAttack>, Option<&mut Energy>), &Enemy>>().iter() { | ||||
|     for (entity, (pos, ranged, energy)) in state.world.query::<hecs::With<(&Position, Option<&mut RangedAttack>, Option<&mut Energy>), &Enemy>>().iter() { | ||||
|         let pos = pos.head(); | ||||
|  | ||||
|         for direction in DIRECTIONS.iter() { | ||||
|             if let Some(target) = positions.get(&(*pos + *direction)) { | ||||
|             if let Some(target) = positions.get(&(pos + *direction)) { | ||||
|                 if let Ok(_) = state.world.get::<&EnemyTarget>(*target) { | ||||
|                     buffer.insert_one(entity, MovingInto(*pos + *direction)); | ||||
|                     buffer.insert_one(entity, MovingInto(pos + *direction)); | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @@ -386,11 +476,12 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|  | ||||
|         // TODO we maybe need a spatial index for this | ||||
|         for (_entity, (target_pos, target)) in state.world.query::<(&Position, &EnemyTarget)>().iter() { | ||||
|             let distance = hex_distance(*pos, target_pos.0); | ||||
|             let target_pos = target_pos.head(); | ||||
|             let distance = hex_distance(pos, target_pos); | ||||
|             if distance < target.aggression_range { | ||||
|                 match closest { | ||||
|                     Some((_pos, old_distance)) if old_distance < distance => closest = Some((target_pos.0, distance)), | ||||
|                     None => closest = Some((target_pos.0, distance)), | ||||
|                     Some((_pos, old_distance)) if old_distance < distance => closest = Some((target_pos, distance)), | ||||
|                     None => closest = Some((target_pos, distance)), | ||||
|                     _ => () | ||||
|                 } | ||||
|             } | ||||
| @@ -400,10 +491,10 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|             if let Some(ranged_attack) = ranged { | ||||
|                 // slightly smart behaviour for ranged attacker: try to stay just within range | ||||
|                 let direction = DIRECTIONS.iter().min_by_key(|dir| | ||||
|                     (hex_distance(*pos + **dir, target_pos) - (ranged_attack.range as i64 - 1)).abs()).unwrap(); | ||||
|                 buffer.insert_one(entity, MovingInto(*pos + *direction)); | ||||
|                     (hex_distance(pos + **dir, target_pos) - (ranged_attack.range as i64 - 1)).abs()).unwrap(); | ||||
|                 buffer.insert_one(entity, MovingInto(pos + *direction)); | ||||
|                 // do ranged attack if valid | ||||
|                 let atk_dir = target_pos - *pos; | ||||
|                 let atk_dir = target_pos - pos; | ||||
|                 if on_axis(atk_dir) && (energy.is_none() || energy.unwrap().try_consume(ranged_attack.energy)) { | ||||
|                     let atk_dir = atk_dir.clamp(-CoordVec::one(), CoordVec::one()); | ||||
|                     buffer.spawn(( | ||||
| @@ -411,23 +502,23 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|                         Enemy, | ||||
|                         Attack { damage: ranged_attack.damage, energy: 0.0 }, | ||||
|                         Velocity(atk_dir), | ||||
|                         Position(*pos), | ||||
|                         Position::single_tile(pos), | ||||
|                         DespawnOnTick(state.ticks.wrapping_add(ranged_attack.range)) | ||||
|                     )); | ||||
|                 } | ||||
|             } else { | ||||
|                 let direction = DIRECTIONS.iter().min_by_key(|dir| hex_distance(*pos + **dir, target_pos)).unwrap(); | ||||
|                 buffer.insert_one(entity, MovingInto(*pos + *direction)); | ||||
|                 let direction = DIRECTIONS.iter().min_by_key(|dir| hex_distance(pos + **dir, target_pos)).unwrap(); | ||||
|                 buffer.insert_one(entity, MovingInto(pos + *direction)); | ||||
|             } | ||||
|         } else { | ||||
|             // wander randomly (ethical) | ||||
|             buffer.insert_one(entity, MovingInto(*pos + *fastrand::choice(DIRECTIONS).unwrap())); | ||||
|             buffer.insert_one(entity, MovingInto(pos + *fastrand::choice(DIRECTIONS).unwrap())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Process velocity | ||||
|     for (entity, (Position(pos), Velocity(vel))) in state.world.query_mut::<(&Position, &Velocity)>() { | ||||
|         buffer.insert_one(entity, MovingInto(*pos + *vel)); | ||||
|     for (entity, (pos, Velocity(vel))) in state.world.query_mut::<(&Position, &Velocity)>() { | ||||
|         buffer.insert_one(entity, MovingInto(pos.head() + *vel)); | ||||
|     } | ||||
|  | ||||
|     buffer.run_on(&mut state.world); | ||||
| @@ -435,6 +526,9 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|     // Process inputs | ||||
|     for (_id, client) in state.clients.iter_mut() { | ||||
|         let mut next_movement = CoordVec::zero(); | ||||
|         let position = state.world.get::<&Position>(client.entity)?.head(); | ||||
|         let mut energy = state.world.get::<&mut Energy>(client.entity)?; | ||||
|         let mut inventory = state.world.get::<&mut Inventory>(client.entity)?; | ||||
|         loop { | ||||
|             let recv = client.inputs_rx.try_recv(); | ||||
|             match recv { | ||||
| @@ -447,28 +541,81 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|                     Input::DownRight => next_movement = CoordVec::new(0, 1), | ||||
|                     Input::DownLeft => next_movement = CoordVec::new(-1, 1), | ||||
|                     Input::Dig => { | ||||
|  | ||||
|                         if terrain_positions.get(&position).is_none() && energy.try_consume(5.0) { | ||||
|                             buffer.spawn(( | ||||
|                                 Terrain, | ||||
|                                 Render('_'), | ||||
|                                 Obstruction { entry_multiplier: 5.0, exit_multiplier: 5.0 }, | ||||
|                                 DespawnOnTick(state.ticks.wrapping_add(StochasticNumber::triangle_from_min_range(5000.0, 5000.0).sample().round() as u64)), | ||||
|                                 Position::single_tile(position) | ||||
|                             )); | ||||
|                             inventory.add(Item::Dirt, StochasticNumber::triangle_from_min_range(1.0, 3.0).sample_rounded()); | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 Err(e) => return Err(e.into()) | ||||
|             } | ||||
|         } | ||||
|         let position = state.world.get::<&mut Position>(client.entity)?.0; | ||||
|          | ||||
|         let target = position + next_movement; | ||||
|         if get_base_terrain(target).can_enter() && target != position { | ||||
|             state.world.insert_one(client.entity, MovingInto(target)).unwrap(); | ||||
|             buffer.insert_one(client.entity, MovingInto(target)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Process motion and attacks | ||||
|     for (entity, (Position(current_pos), MovingInto(target_pos), damage, mut energy, move_cost, despawn_on_impact)) in state.world.query::<(&mut Position, &MovingInto, Option<&mut Attack>, Option<&mut Energy>, Option<&MoveCost>, Option<&DespawnOnImpact>)>().iter() { | ||||
|         let mut move_cost = move_cost.map(|x| x.0.sample()).unwrap_or(0.0); | ||||
|         if let Some(current_terrain) = terrain_positions.get(current_pos) { | ||||
|             move_cost += 1.0; | ||||
|     buffer.run_on(&mut state.world); | ||||
|  | ||||
|     let mut despawn_buffer = HashSet::new(); | ||||
|  | ||||
|     // This might lead to a duping glitch, which would at least be funny. | ||||
|     let kill = |buffer: &mut CommandBuffer, despawn_buffer: &mut HashSet<Entity>, state: &GameState, entity: Entity, killer: Option<Entity>, position: Option<Coord>| { | ||||
|         let position = position.unwrap_or_else(|| state.world.get::<&Position>(entity).unwrap().head()); | ||||
|         despawn_buffer.insert(entity); | ||||
|         buffer.despawn(entity); | ||||
|         let mut materialized_drops = Inventory::empty(); | ||||
|         if let Ok(drops) = state.world.get::<&Drops>(entity) { | ||||
|             for (drop, frequency) in drops.0.iter() { | ||||
|                 materialized_drops.add(drop.clone(), frequency.sample_rounded()) | ||||
|             } | ||||
|         // TODO will break attacks kind of, desirable? Doubtful. | ||||
|         } | ||||
|         if let Ok(other_inv) = state.world.get::<&Inventory>(entity) { | ||||
|             materialized_drops.extend(&other_inv); | ||||
|         } | ||||
|         let killer_consumed_items = if let Some(killer) = killer { | ||||
|             if let Ok(mut inv) = state.world.get::<&mut Inventory>(killer) { | ||||
|                 inv.extend(&materialized_drops); | ||||
|                 true | ||||
|             } else { | ||||
|                 false | ||||
|             } | ||||
|         } else { false }; | ||||
|         if !killer_consumed_items && !materialized_drops.is_empty() { | ||||
|             buffer.spawn(( | ||||
|                 Position::single_tile(position), | ||||
|                 Render('☒'), | ||||
|                 materialized_drops | ||||
|             )); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     // Process motion and attacks | ||||
|     for (entity, (current_pos, MovingInto(target_pos), damage, mut energy, move_cost, despawn_on_impact)) in state.world.query::<(&mut Position, &MovingInto, Option<&mut Attack>, Option<&mut Energy>, Option<&MoveCost>, Option<&DespawnOnImpact>)>().iter() { | ||||
|         let mut move_cost = move_cost.map(|x| x.0.sample()).unwrap_or(0.0); | ||||
|          | ||||
|         for tile in current_pos.0.iter() { | ||||
|             // TODO: perhaps large enemies should not be exponentially more vulnerable to environmental hazards | ||||
|             if let Some(current_terrain) = terrain_positions.get(tile) { | ||||
|                 if let Ok(obstruction) = state.world.get::<&Obstruction>(*current_terrain) { | ||||
|                     move_cost *= obstruction.exit_multiplier; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // TODO: attacks into obstructions are still cheap; is this desirable? | ||||
|         if let Some(target_terrain) = terrain_positions.get(target_pos) { | ||||
|             move_cost += 1.0; | ||||
|             if let Ok(obstruction) = state.world.get::<&Obstruction>(*target_terrain) { | ||||
|                 move_cost *= obstruction.entry_multiplier; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if get_base_terrain(*target_pos).can_enter() { | ||||
| @@ -485,16 +632,16 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|                             _ => () | ||||
|                         } | ||||
|                         if despawn_on_impact.is_some() { | ||||
|                             buffer.despawn(entity); | ||||
|                             kill(&mut buffer, &mut despawn_buffer, &state, entity, Some(target_entity), Some(*target_pos)); | ||||
|                         } | ||||
|                         if x.0 <= 0.0 { | ||||
|                             buffer.despawn(target_entity); | ||||
|                         if x.0 < 0.0 { | ||||
|                             kill(&mut buffer, &mut despawn_buffer, &state, target_entity, Some(entity), Some(*target_pos)); | ||||
|                             Some(Entry::Occupied(o)) | ||||
|                         } else { | ||||
|                             None | ||||
|                         } | ||||
|                     } else { | ||||
|                         None // TODO: on pickup or something | ||||
|                         None // no "on pickup" exists; emulated with health 0 | ||||
|                     } | ||||
|                 }, | ||||
|                 Entry::Vacant(v) => Some(Entry::Vacant(v)) | ||||
| @@ -503,8 +650,8 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|                 // TODO: perhaps this should be applied to attacks too? | ||||
|                 if consume_energy_if_available(&mut energy, move_cost) { | ||||
|                     *entry.or_insert(entity) = entity; | ||||
|                     positions.remove(current_pos); | ||||
|                     *current_pos = *target_pos; | ||||
|                     positions.remove(¤t_pos.0.pop_back().unwrap()); | ||||
|                     current_pos.0.push_front(*target_pos); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -518,26 +665,37 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|     } | ||||
|  | ||||
|     // Process transient entities | ||||
|     for (entity, tick) in state.world.query_mut::<&DespawnOnTick>() { | ||||
|     for (entity, tick) in state.world.query::<&DespawnOnTick>().iter() { | ||||
|         if state.ticks == tick.0 { | ||||
|             buffer.despawn(entity); | ||||
|             kill(&mut buffer, &mut despawn_buffer, &state, entity, None, None); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     for (entity, DespawnRandomly(inv_rate)) in state.world.query_mut::<&DespawnRandomly>() { | ||||
|     for (entity, DespawnRandomly(inv_rate)) in state.world.query::<&DespawnRandomly>().iter() { | ||||
|         if fastrand::u64(0..*inv_rate) == 0 { | ||||
|             buffer.despawn(entity); | ||||
|             kill(&mut buffer, &mut despawn_buffer, &state, entity, None, None); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     buffer.run_on(&mut state.world); | ||||
|  | ||||
|     let mut delete = vec![]; | ||||
|     for (position, entity) in positions.iter() { | ||||
|         if despawn_buffer.contains(entity) { | ||||
|             delete.push(*position); | ||||
|         } | ||||
|     } | ||||
|     for position in delete { | ||||
|         positions.remove(&position); | ||||
|     } | ||||
|  | ||||
|     // Send views to clients | ||||
|     // TODO: terrain layer below others | ||||
|     for (_id, client) in state.clients.iter() { | ||||
|         client.frames_tx.send(Frame::PlayerCount(state.clients.len())).await?; | ||||
|         let mut nearby = vec![]; | ||||
|         if let Ok(pos) = state.world.get::<&Position>(client.entity) { | ||||
|             let pos = pos.0; | ||||
|             let pos = pos.head(); | ||||
|             for q in -VIEW..=VIEW { | ||||
|                 for r in (-VIEW).max(-q - VIEW)..= VIEW.min(-q+VIEW) { | ||||
|                     let offset = CoordVec::new(q, r); | ||||
| @@ -548,9 +706,12 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|                         if let Some(entity) = positions.get(&pos) { | ||||
|                             let render = state.world.get::<&Render>(*entity)?; | ||||
|                             let health = if let Ok(h) = state.world.get::<&Health>(*entity) { | ||||
|                                 h.0 / h.1 | ||||
|                                 h.pct() | ||||
|                             } else { 1.0 }; | ||||
|                             nearby.push((q, r, render.0, health)) | ||||
|                             nearby.push((q, r, render.0, health)); | ||||
|                         } else if let Some(entity) = terrain_positions.get(&pos) { | ||||
|                             let render = state.world.get::<&Render>(*entity)?; | ||||
|                             nearby.push((q, r, render.0, 1.0)); | ||||
|                         } else { | ||||
|                             let mut rng = rng_from_hash(pos); | ||||
|                             let bg = if rng.usize(0..10) == 0 { ',' } else { '.' }; | ||||
| @@ -560,7 +721,9 @@ async fn game_tick(state: &mut GameState) -> Result<()> { | ||||
|                 } | ||||
|             } | ||||
|             let health = state.world.get::<&Health>(client.entity)?.0; | ||||
|             client.frames_tx.send(Frame::Display { nearby, health }).await?; | ||||
|             let inventory = state.world.get::<&Inventory>(client.entity)?.0 | ||||
|                 .iter().map(|(i, q)| (i.name().to_string(), i.description().to_string(), *q)).filter(|(_, _, q)| *q > 0).collect(); | ||||
|             client.frames_tx.send(Frame::Display { nearby, health, inventory }).await?; | ||||
|         } else { | ||||
|             client.frames_tx.send(Frame::Dead).await?; | ||||
|         } | ||||
| @@ -599,7 +762,7 @@ fn add_new_player(state: &mut GameState) -> Result<Entity> { | ||||
|         } | ||||
|     }; | ||||
|     Ok(state.world.spawn(( | ||||
|         Position(pos), | ||||
|         Position::single_tile(pos), | ||||
|         PlayerCharacter, | ||||
|         Render(random_identifier()), | ||||
|         Collidable, | ||||
| @@ -610,7 +773,9 @@ fn add_new_player(state: &mut GameState) -> Result<Entity> { | ||||
|             spawn_range: 3..=10, | ||||
|             spawn_rate_inv: 20, | ||||
|             aggression_range: 5 | ||||
|         } | ||||
|         }, | ||||
|         Energy { current: 0.0, regeneration_rate: 1.0, burst: 5.0 }, | ||||
|         Inventory::empty() | ||||
|     ))) | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										177
									
								
								static/app.js
									
									
									
									
									
								
							
							
						
						
									
										177
									
								
								static/app.js
									
									
									
									
									
								
							| @@ -387,18 +387,23 @@ | ||||
|   var { window: window_1 } = globals; | ||||
|   function get_each_context(ctx, list, i) { | ||||
|     const child_ctx = ctx.slice(); | ||||
|     child_ctx[17] = list[i]; | ||||
|     child_ctx[19] = i; | ||||
|     child_ctx[18] = list[i]; | ||||
|     return child_ctx; | ||||
|   } | ||||
|   function get_each_context_1(ctx, list, i) { | ||||
|     const child_ctx = ctx.slice(); | ||||
|     child_ctx[20] = list[i]; | ||||
|     child_ctx[21] = list[i]; | ||||
|     child_ctx[23] = i; | ||||
|     return child_ctx; | ||||
|   } | ||||
|   function create_each_block_1(ctx) { | ||||
|   function get_each_context_2(ctx, list, i) { | ||||
|     const child_ctx = ctx.slice(); | ||||
|     child_ctx[24] = list[i]; | ||||
|     return child_ctx; | ||||
|   } | ||||
|   function create_each_block_2(ctx) { | ||||
|     let div; | ||||
|     let t_value = ctx[20][0] + ""; | ||||
|     let t_value = ctx[24][0] + ""; | ||||
|     let t; | ||||
|     let div_style_value; | ||||
|     return { | ||||
| @@ -406,16 +411,16 @@ | ||||
|         div = element("div"); | ||||
|         t = text(t_value); | ||||
|         attr(div, "class", "cell svelte-oncm9j"); | ||||
|         attr(div, "style", div_style_value = `width: ${ctx[5]}px; height: ${ctx[6]}px; line-height: ${ctx[6]}px; opacity: ${ctx[20][1] * 100}%`); | ||||
|         attr(div, "style", div_style_value = `width: ${ctx[6]}px; height: ${ctx[7]}px; line-height: ${ctx[7]}px; opacity: ${ctx[24][1] * 100}%`); | ||||
|       }, | ||||
|       m(target, anchor) { | ||||
|         insert(target, div, anchor); | ||||
|         append(div, t); | ||||
|       }, | ||||
|       p(ctx2, dirty) { | ||||
|         if (dirty & 8 && t_value !== (t_value = ctx2[20][0] + "")) | ||||
|         if (dirty & 16 && t_value !== (t_value = ctx2[24][0] + "")) | ||||
|           set_data(t, t_value); | ||||
|         if (dirty & 8 && div_style_value !== (div_style_value = `width: ${ctx2[5]}px; height: ${ctx2[6]}px; line-height: ${ctx2[6]}px; opacity: ${ctx2[20][1] * 100}%`)) { | ||||
|         if (dirty & 16 && div_style_value !== (div_style_value = `width: ${ctx2[6]}px; height: ${ctx2[7]}px; line-height: ${ctx2[7]}px; opacity: ${ctx2[24][1] * 100}%`)) { | ||||
|           attr(div, "style", div_style_value); | ||||
|         } | ||||
|       }, | ||||
| @@ -425,14 +430,14 @@ | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|   function create_each_block(ctx) { | ||||
|   function create_each_block_1(ctx) { | ||||
|     let div; | ||||
|     let t; | ||||
|     let div_style_value; | ||||
|     let each_value_1 = ctx[17]; | ||||
|     let each_value_2 = ctx[21]; | ||||
|     let each_blocks = []; | ||||
|     for (let i = 0; i < each_value_1.length; i += 1) { | ||||
|       each_blocks[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i)); | ||||
|     for (let i = 0; i < each_value_2.length; i += 1) { | ||||
|       each_blocks[i] = create_each_block_2(get_each_context_2(ctx, each_value_2, i)); | ||||
|     } | ||||
|     return { | ||||
|       c() { | ||||
| @@ -442,7 +447,7 @@ | ||||
|         } | ||||
|         t = space(); | ||||
|         attr(div, "class", "row svelte-oncm9j"); | ||||
|         attr(div, "style", div_style_value = `height: ${ctx[6]}px; ` + (ctx[19] % 2 === 1 ? `padding-left: ${ctx[5] / 2}px` : "")); | ||||
|         attr(div, "style", div_style_value = `height: ${ctx[7]}px; ` + (ctx[23] % 2 === 1 ? `padding-left: ${ctx[6] / 2}px` : "")); | ||||
|       }, | ||||
|       m(target, anchor) { | ||||
|         insert(target, div, anchor); | ||||
| @@ -452,15 +457,15 @@ | ||||
|         append(div, t); | ||||
|       }, | ||||
|       p(ctx2, dirty) { | ||||
|         if (dirty & 104) { | ||||
|           each_value_1 = ctx2[17]; | ||||
|         if (dirty & 208) { | ||||
|           each_value_2 = ctx2[21]; | ||||
|           let i; | ||||
|           for (i = 0; i < each_value_1.length; i += 1) { | ||||
|             const child_ctx = get_each_context_1(ctx2, each_value_1, i); | ||||
|           for (i = 0; i < each_value_2.length; i += 1) { | ||||
|             const child_ctx = get_each_context_2(ctx2, each_value_2, i); | ||||
|             if (each_blocks[i]) { | ||||
|               each_blocks[i].p(child_ctx, dirty); | ||||
|             } else { | ||||
|               each_blocks[i] = create_each_block_1(child_ctx); | ||||
|               each_blocks[i] = create_each_block_2(child_ctx); | ||||
|               each_blocks[i].c(); | ||||
|               each_blocks[i].m(div, t); | ||||
|             } | ||||
| @@ -468,7 +473,7 @@ | ||||
|           for (; i < each_blocks.length; i += 1) { | ||||
|             each_blocks[i].d(1); | ||||
|           } | ||||
|           each_blocks.length = each_value_1.length; | ||||
|           each_blocks.length = each_value_2.length; | ||||
|         } | ||||
|       }, | ||||
|       d(detaching) { | ||||
| @@ -497,7 +502,7 @@ | ||||
|         insert(target, a, anchor); | ||||
|         insert(target, t2, anchor); | ||||
|         if (!mounted) { | ||||
|           dispose = listen(a, "click", ctx[4]); | ||||
|           dispose = listen(a, "click", ctx[5]); | ||||
|           mounted = true; | ||||
|         } | ||||
|       }, | ||||
| @@ -567,6 +572,47 @@ | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|   function create_each_block(ctx) { | ||||
|     let li; | ||||
|     let t0_value = ctx[18][0] + ""; | ||||
|     let t0; | ||||
|     let t1; | ||||
|     let t2_value = ctx[18][2] + ""; | ||||
|     let t2; | ||||
|     let t3; | ||||
|     let t4_value = ctx[18][1] + ""; | ||||
|     let t4; | ||||
|     return { | ||||
|       c() { | ||||
|         li = element("li"); | ||||
|         t0 = text(t0_value); | ||||
|         t1 = text(" x"); | ||||
|         t2 = text(t2_value); | ||||
|         t3 = text(": "); | ||||
|         t4 = text(t4_value); | ||||
|       }, | ||||
|       m(target, anchor) { | ||||
|         insert(target, li, anchor); | ||||
|         append(li, t0); | ||||
|         append(li, t1); | ||||
|         append(li, t2); | ||||
|         append(li, t3); | ||||
|         append(li, t4); | ||||
|       }, | ||||
|       p(ctx2, dirty) { | ||||
|         if (dirty & 8 && t0_value !== (t0_value = ctx2[18][0] + "")) | ||||
|           set_data(t0, t0_value); | ||||
|         if (dirty & 8 && t2_value !== (t2_value = ctx2[18][2] + "")) | ||||
|           set_data(t2, t2_value); | ||||
|         if (dirty & 8 && t4_value !== (t4_value = ctx2[18][1] + "")) | ||||
|           set_data(t4, t4_value); | ||||
|       }, | ||||
|       d(detaching) { | ||||
|         if (detaching) | ||||
|           detach(li); | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
|   function create_fragment(ctx) { | ||||
|     let h1; | ||||
|     let t1; | ||||
| @@ -576,16 +622,23 @@ | ||||
|     let div1; | ||||
|     let t3; | ||||
|     let t4; | ||||
|     let t5; | ||||
|     let ul; | ||||
|     let mounted; | ||||
|     let dispose; | ||||
|     let each_value_1 = ctx[4]; | ||||
|     let each_blocks_1 = []; | ||||
|     for (let i = 0; i < each_value_1.length; i += 1) { | ||||
|       each_blocks_1[i] = create_each_block_1(get_each_context_1(ctx, each_value_1, i)); | ||||
|     } | ||||
|     let if_block0 = ctx[0] && create_if_block_2(ctx); | ||||
|     let if_block1 = ctx[2] && create_if_block_1(ctx); | ||||
|     let if_block2 = ctx[1] && create_if_block(ctx); | ||||
|     let each_value = ctx[3]; | ||||
|     let each_blocks = []; | ||||
|     for (let i = 0; i < each_value.length; i += 1) { | ||||
|       each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); | ||||
|     } | ||||
|     let if_block0 = ctx[0] && create_if_block_2(ctx); | ||||
|     let if_block1 = ctx[2] && create_if_block_1(ctx); | ||||
|     let if_block2 = ctx[1] && create_if_block(ctx); | ||||
|     return { | ||||
|       c() { | ||||
|         h1 = element("h1"); | ||||
| @@ -593,8 +646,8 @@ | ||||
|         t1 = space(); | ||||
|         div2 = element("div"); | ||||
|         div0 = element("div"); | ||||
|         for (let i = 0; i < each_blocks.length; i += 1) { | ||||
|           each_blocks[i].c(); | ||||
|         for (let i = 0; i < each_blocks_1.length; i += 1) { | ||||
|           each_blocks_1[i].c(); | ||||
|         } | ||||
|         t2 = space(); | ||||
|         div1 = element("div"); | ||||
| @@ -606,6 +659,11 @@ | ||||
|         t4 = space(); | ||||
|         if (if_block2) | ||||
|           if_block2.c(); | ||||
|         t5 = space(); | ||||
|         ul = element("ul"); | ||||
|         for (let i = 0; i < each_blocks.length; i += 1) { | ||||
|           each_blocks[i].c(); | ||||
|         } | ||||
|         attr(div0, "class", "game-display svelte-oncm9j"); | ||||
|         attr(div1, "class", "controls"); | ||||
|         attr(div2, "class", "wrapper svelte-oncm9j"); | ||||
| @@ -615,8 +673,8 @@ | ||||
|         insert(target, t1, anchor); | ||||
|         insert(target, div2, anchor); | ||||
|         append(div2, div0); | ||||
|         for (let i = 0; i < each_blocks.length; i += 1) { | ||||
|           each_blocks[i].m(div0, null); | ||||
|         for (let i = 0; i < each_blocks_1.length; i += 1) { | ||||
|           each_blocks_1[i].m(div0, null); | ||||
|         } | ||||
|         append(div2, t2); | ||||
|         append(div2, div1); | ||||
| @@ -628,32 +686,37 @@ | ||||
|         append(div1, t4); | ||||
|         if (if_block2) | ||||
|           if_block2.m(div1, null); | ||||
|         append(div1, t5); | ||||
|         append(div1, ul); | ||||
|         for (let i = 0; i < each_blocks.length; i += 1) { | ||||
|           each_blocks[i].m(ul, null); | ||||
|         } | ||||
|         if (!mounted) { | ||||
|           dispose = [ | ||||
|             listen(window_1, "keydown", ctx[7]), | ||||
|             listen(window_1, "keyup", ctx[8]) | ||||
|             listen(window_1, "keydown", ctx[8]), | ||||
|             listen(window_1, "keyup", ctx[9]) | ||||
|           ]; | ||||
|           mounted = true; | ||||
|         } | ||||
|       }, | ||||
|       p(ctx2, [dirty]) { | ||||
|         if (dirty & 104) { | ||||
|           each_value = ctx2[3]; | ||||
|         if (dirty & 208) { | ||||
|           each_value_1 = ctx2[4]; | ||||
|           let i; | ||||
|           for (i = 0; i < each_value.length; i += 1) { | ||||
|             const child_ctx = get_each_context(ctx2, each_value, i); | ||||
|             if (each_blocks[i]) { | ||||
|               each_blocks[i].p(child_ctx, dirty); | ||||
|           for (i = 0; i < each_value_1.length; i += 1) { | ||||
|             const child_ctx = get_each_context_1(ctx2, each_value_1, i); | ||||
|             if (each_blocks_1[i]) { | ||||
|               each_blocks_1[i].p(child_ctx, dirty); | ||||
|             } else { | ||||
|               each_blocks[i] = create_each_block(child_ctx); | ||||
|               each_blocks[i].c(); | ||||
|               each_blocks[i].m(div0, null); | ||||
|               each_blocks_1[i] = create_each_block_1(child_ctx); | ||||
|               each_blocks_1[i].c(); | ||||
|               each_blocks_1[i].m(div0, null); | ||||
|             } | ||||
|           } | ||||
|           for (; i < each_blocks.length; i += 1) { | ||||
|             each_blocks[i].d(1); | ||||
|           for (; i < each_blocks_1.length; i += 1) { | ||||
|             each_blocks_1[i].d(1); | ||||
|           } | ||||
|           each_blocks.length = each_value.length; | ||||
|           each_blocks_1.length = each_value_1.length; | ||||
|         } | ||||
|         if (ctx2[0]) { | ||||
|           if (if_block0) { | ||||
| @@ -685,12 +748,30 @@ | ||||
|           } else { | ||||
|             if_block2 = create_if_block(ctx2); | ||||
|             if_block2.c(); | ||||
|             if_block2.m(div1, null); | ||||
|             if_block2.m(div1, t5); | ||||
|           } | ||||
|         } else if (if_block2) { | ||||
|           if_block2.d(1); | ||||
|           if_block2 = null; | ||||
|         } | ||||
|         if (dirty & 8) { | ||||
|           each_value = ctx2[3]; | ||||
|           let i; | ||||
|           for (i = 0; i < each_value.length; i += 1) { | ||||
|             const child_ctx = get_each_context(ctx2, each_value, i); | ||||
|             if (each_blocks[i]) { | ||||
|               each_blocks[i].p(child_ctx, dirty); | ||||
|             } else { | ||||
|               each_blocks[i] = create_each_block(child_ctx); | ||||
|               each_blocks[i].c(); | ||||
|               each_blocks[i].m(ul, null); | ||||
|             } | ||||
|           } | ||||
|           for (; i < each_blocks.length; i += 1) { | ||||
|             each_blocks[i].d(1); | ||||
|           } | ||||
|           each_blocks.length = each_value.length; | ||||
|         } | ||||
|       }, | ||||
|       i: noop, | ||||
|       o: noop, | ||||
| @@ -701,13 +782,14 @@ | ||||
|           detach(t1); | ||||
|         if (detaching) | ||||
|           detach(div2); | ||||
|         destroy_each(each_blocks, detaching); | ||||
|         destroy_each(each_blocks_1, detaching); | ||||
|         if (if_block0) | ||||
|           if_block0.d(); | ||||
|         if (if_block1) | ||||
|           if_block1.d(); | ||||
|         if (if_block2) | ||||
|           if_block2.d(); | ||||
|         destroy_each(each_blocks, detaching); | ||||
|         mounted = false; | ||||
|         run_all(dispose); | ||||
|       } | ||||
| @@ -719,6 +801,7 @@ | ||||
|     let dead = false; | ||||
|     let health; | ||||
|     let players; | ||||
|     let inventory = []; | ||||
|     let ws; | ||||
|     const connect = () => { | ||||
|       ws = new WebSocket(window.location.protocol === "https:" ? "wss://ewo.osmarks.net/" : "ws://localhost:8080/"); | ||||
| @@ -731,8 +814,9 @@ | ||||
|             const row = r; | ||||
|             newGrid[row + OFFSET][col + OFFSET] = [c, o]; | ||||
|           } | ||||
|           $$invalidate(3, grid = newGrid); | ||||
|           $$invalidate(4, grid = newGrid); | ||||
|           $$invalidate(1, health = data.Display.health); | ||||
|           $$invalidate(3, inventory = data.Display.inventory); | ||||
|         } | ||||
|         if (data === "Dead") { | ||||
|           $$invalidate(0, dead = true); | ||||
| @@ -782,10 +866,11 @@ | ||||
|       "a": "Left", | ||||
|       "d": "Right", | ||||
|       "z": "DownLeft", | ||||
|       "x": "DownRight" | ||||
|       "x": "DownRight", | ||||
|       "f": "Dig" | ||||
|     }; | ||||
|     connect(); | ||||
|     return [dead, health, players, grid, restart, HORIZ, VERT, keydown, keyup]; | ||||
|     return [dead, health, players, inventory, grid, restart, HORIZ, VERT, keydown, keyup]; | ||||
|   } | ||||
|   var App = class extends SvelteComponent { | ||||
|     constructor(options) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user