Compare commits

..

10 Commits

Author SHA1 Message Date
Elijah Voigt 73c1196a81 Only click and drag trees in debug mode 3 months ago
Elijah Voigt 4854ba5937 Very simple click and dragging 3 months ago
Elijah Voigt e056503a2e Copy in Sams temp gibli style assets (NO AI) 3 months ago
Elijah Voigt 1c7dba07c0 Load all monologues in wasm build (hopefully) by loading each one by one 4 months ago
Elijah Voigt b1832b6349 Add work-in-progress monologue assets 4 months ago
Elijah Voigt 1d31902312 Organize Monologue into more specific types
Monologue contains a vec of MonologueGroups
MonologueGroup contains a vec of MonologueLines

This would make it easier (ish) to add @directives like I've spec'd out
We can add a Vec of Directive enums at each level.
4 months ago
Elijah Voigt a96e86ea9b Add name to base game, clean up for web release 4 months ago
Elijah Voigt 7b813b54f9 Add readme for trees game 4 months ago
Elijah Voigt 7d1dcc9de2 Add "script" tooltip for trees not just buttons 4 months ago
Elijah Voigt ff6609cb2f Used hash for coordinates like a smart person 4 months ago

3
.gitignore vendored

@ -11,3 +11,6 @@ dist/*
# Generated VERSION file for builds # Generated VERSION file for builds
VERSION VERSION
# Generated MONOLOGUES list for wasm build
assets/trees/MONOLOGUES

1
Cargo.lock generated

@ -2228,6 +2228,7 @@ dependencies = [
"chrono", "chrono",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.12",
"walkdir",
] ]
[[package]] [[package]]

@ -19,3 +19,4 @@ features = ["wayland", "dynamic_linking"]
[build-dependencies] [build-dependencies]
chrono = "*" chrono = "*"
walkdir = "*"

@ -7,7 +7,7 @@ dist/trees:
mkdir -p dist/trees mkdir -p dist/trees
# Build the web version # Build the web version
release/trees/web: release/trees/web: src/bin/trees/main.rs src/bin/trees/mono.rs
cargo build --bin trees --release --target wasm32-unknown-unknown cargo build --bin trees --release --target wasm32-unknown-unknown
# Use wasm-bindgen to do some magic # Use wasm-bindgen to do some magic

@ -0,0 +1 @@
How much of me bends in the wind?

@ -0,0 +1 @@
Something feels different today..

@ -0,0 +1 @@
>My life had a beginning... and it continues... so I suppose it must end sometime?

@ -1,19 +1,9 @@
###
# Industry
# Written by Elijah Voigt
# No copyright, it's bad on purpose
###
industry industry
--- ---
cars drone in the distance with no sign of stopping... cars drone in the distance with no sign of stopping...
the roar of a jet echos in the sky somewhere between takeoff and landing... the roar of a jet echos in the sky somewhere between takeoff and landing...
a train in the distance warns of it's imminent arrival... a train in the distance warns of it's imminent arrival...
--- ---
industry industry

@ -0,0 +1 @@
I think I understand my place now.

@ -0,0 +1,5 @@
A: Are you still there?
---
B: I am still here.
---
A: Oh good.

@ -0,0 +1 @@
Yes... Things are as they should be.

@ -0,0 +1 @@
Oooh thats brisk!

@ -0,0 +1,3 @@
Surrounding oneself with death is another way of embracing life, right?
---
The cycle never really ends.

@ -0,0 +1 @@
No sense keeping these around much longer..

@ -0,0 +1 @@
My leaves feel funny.

@ -0,0 +1,3 @@
Everyone seems to be slowing down a bit.
---
Good thing Ill stay green forever!

@ -0,0 +1,5 @@
A: Im getting sleepy... is it time?
---
B: Well since youre doing it, I suppose Ill follow suit. Who would I talk to through the winter with you hibernating, anyway?
---
A: Wwwwwwwwww (like Zzzzz but for trees)

@ -1,27 +0,0 @@
a
b
c
---
d
e
f
---
g
h
---
i
j
---
k

@ -1,29 +0,0 @@
# This empty dialog should be ignored
---
# This is a comment
this is one line of dialog
this is another options
# This is another comment
---
# this is
# a lot
# of comments
# back to back
together they can make poetry
together they can tell a story
# and a few more for good measure
---
# This should be ignored
---
# This too
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
assets/trees/images/Ghibli_Trees_4K.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/green-a.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/green-a.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/green-b.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/green-b.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/green-c.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/green-c.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/orange-a.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/orange-a.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/orange-b.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/orange-b.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/orange-c.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/orange-c.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/pink-a.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/pink-a.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/pink-b.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/pink-b.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/pink-c.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/pink-c.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/yellow-a.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/yellow-a.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/yellow-b.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/yellow-b.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/yellow-c.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/trees/images/yellow-c.xcf (Stored with Git LFS)

Binary file not shown.

@ -0,0 +1 @@
I hope those bugs down there are friendly.

@ -0,0 +1,3 @@
Slumber is refreshing
---
It feels good to move again.

@ -0,0 +1,11 @@
A: Hey, are you awake yet?
---
B: Wwwwwwwww
---
A: Wake up! Its warmer and there is food!
---
B: Www...
---
The greats always sleep
In more ways than one can hope
Restoration dawns

@ -0,0 +1 @@
I could eat all day.

@ -0,0 +1,7 @@
Did anyone else feel that?
---
No, just me?
---
Ok...
---
Ill just stay put.

@ -0,0 +1,13 @@
A: I dont feel so good.
---
Its that infested sort of feeling.
B: Oh no, they dont feel so good.
---
Its that infested sort of feeling.
C: Hm... sounds like...
---
that infested sort of feeling.
---
Better make some bug repellents.

@ -0,0 +1 @@
The sun is powerful and long lasting.

@ -0,0 +1 @@
The heat is different this time... something doesnt feel right.

@ -0,0 +1 @@
Warm seasons past are all blending in to one, and I swear it feels just like this warm season.

@ -0,0 +1,5 @@
Of all the ways to be, I've chosen to be this way. There isn't exactly a reason why, but still I hold on to my way as if my life depends on it.
---
Perhaps I am this way because my life experience up to this point has called for it, but who's to say my life circumstances won't change in an instant, and I need to adjust immediately?
---
Ew. Yellow in July.

@ -0,0 +1 @@
This cold is making me ache.

@ -0,0 +1,3 @@
ugh... I am so lazy. I should probably try to just make one leaf or something, right? I mean, other trees keep their leaves in the winter.
---
Ill do it tomorrow.

@ -1,8 +1,14 @@
fn main() {
{
use chrono::prelude::*;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
fn main() {
write_version_file();
write_monologues_file();
}
fn write_version_file() {
use chrono::prelude::*;
use std::process::Command; use std::process::Command;
// Date of build // Date of build
@ -37,4 +43,17 @@ fn main() {
.expect("Write version to VERSION file"); .expect("Write version to VERSION file");
} }
} }
fn write_monologues_file() {
let mut file = File::create("assets/trees/MONOLOGUES").expect("Create MONOLOGUES file");
walkdir::WalkDir::new("assets/trees")
.into_iter()
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry.path().extension() == Some(std::ffi::OsStr::new("mono"))
})
.for_each(|entry| {
let _ = writeln!(file, "{}", entry.path().to_string_lossy().strip_prefix("assets/").unwrap());
});
} }

@ -11,7 +11,7 @@ use games::*;
/// This shows how to do that. /// This shows how to do that.
fn main() { fn main() {
App::new() App::new()
.add_plugins(BaseGamePlugin) .add_plugins(BaseGamePlugin::default())
.init_resource::<InsideArea>() .init_resource::<InsideArea>()
.add_systems( .add_systems(
Startup, Startup,

@ -3,7 +3,7 @@ use games::*;
fn main() { fn main() {
App::new() App::new()
.init_resource::<Thing>() .init_resource::<Thing>()
.add_plugins(BaseGamePlugin) .add_plugins(BaseGamePlugin::default())
.add_systems(Startup, init_ui) .add_systems(Startup, init_ui)
.add_systems( .add_systems(
Update, Update,

@ -1,14 +1,24 @@
use super::*; use super::*;
/// A good starting place for creating a game building on top of the base Bevy app /// A good starting place for creating a game building on top of the base Bevy app
pub struct BaseGamePlugin; pub struct BaseGamePlugin {
pub name: String,
}
impl Default for BaseGamePlugin {
fn default() -> Self {
BaseGamePlugin {
name: "mygame".into(),
}
}
}
impl Plugin for BaseGamePlugin { impl Plugin for BaseGamePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(DefaultPlugins.set(WindowPlugin { app.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
fit_canvas_to_parent: true, fit_canvas_to_parent: true,
canvas: Some("game".into()), canvas: Some(format!("#{}-canvas", self.name)),
..default() ..default()
}), }),
..default() ..default()

@ -1,5 +1,7 @@
use games::*; use games::*;
fn main() { fn main() {
App::new().add_plugins(BaseGamePlugin).run(); App::new()
.add_plugins(BaseGamePlugin { name: "hum".into() })
.run();
} }

@ -1,5 +1,9 @@
use games::*; use games::*;
fn main() { fn main() {
App::new().add_plugins(BaseGamePlugin).run(); App::new()
.add_plugins(BaseGamePlugin {
name: "tetris-rpg".into(),
})
.run();
} }

@ -0,0 +1,193 @@
# 🌲 Trees 🌳
## How to Play
> currently in hella alpha so subject to change and/or be out of date
* Left click a tree to start a Monologue
* Right click + drag to move a tree
* Middle Click to delete a tree
* F12: Debug Menu
* Click a monologue script to spawn a tree
## Monologue File Format
In `assets/trees/` there are `.mono` files that contain the monologues that drive this game.
Here is an example of a simple monologue:
```
This is a simple monologue
There are two options for this fist batch
---
This is the second batch of lines
There are three options
You get the idea
---
This has one option
```
* Files contain "Monologues"
* Monologues are broken up into "Line Option" batches
* Lines are separated by one or more blank newlines
* Batches are broken up by lines with `---`
> Monologue vs monologue, Batch vs batch, and Line vs line.
> The capitalized versions specify a game object, the lower case specifies the common meaning of the word.
### Comments
Comments are lines that start with a `#`.
Comments are discarded by the parser so are only for the reader of the raw text files.
```
# This is a comment
This is a dialog line
# If you connect two lines with a comment
They count as part of the same line
# Comments can exist on their own
This line of monologue # Will include the comment
# So always start a line with the comment
```
### Empty batches
Batches with no lines are a no-op.
Even if the batches contain comments and/or directives.
## Monologue Directives
> This feature is in planning, not implmeneted
Monologue Directives are basically how we "script" monologues.
With directives you can:
* Set variables based on what a user sees or does.
* Add pre-requisites to a line or entire Monologue.
* Requirement that an "actor" meets some criteria.
In general directives can be set at the Monologue, Batch, and the Line scopes.
* At the Monologue scope they must be set at the top before any batches.
* At the Batch scope they must be separated from Lines by at least one blank line.
* At the Line scope they must be on an adjacent Line.
Example:
```
# This directive affects the monologue as a whole
@directive
---
# This directive affects this entire group
@directive
this is a line
# This directive just affects this line
@directive
this is another line
# This directive also affects the entire group
@directive
last line in this batch
---
# This batch has no directives
option one
option two
```
### `@set var val`
The `@set` directive sets a variable to a given value upon the user interacting with the affected scope.
* At the Monologue scope this is set when the monologue starts.
* At the Batch scope this is set when the user views the Batch.
* At the Line scope this is set iff the user chooses the Line.
Example:
```
# Monologue scope
@set foo 1
---
# batch scope
@set bar true
# Line scope
@set baz "value"
this line sets baz to "value"
---
this batch...
...has no directives
```
### `@if var op val`
The `@if` directive is a constraint requiring `var op val` to return true for the scope to be visible.
* At the Monologue scope this causes the monologue to either be or not be in rotation.
* At the Batch scope this causes the Batch to be included or skipped from a Monologue.
* At the Line scope this causes the line to be included or excluded from a Batch.
### `@has var op val`
The `@has` directive constrains which trees can deliver a given monologue.
For example if a tree has `family` set to `fir` it could deliver a monologue about being a fir tree.
```
@has family = "fir"
---
man i love being a fir tree
```
At all scopes this operates similarly to `@if`, skipping/including Monologues/Groups/Lines based on tree properties.
### Other possible directives
The following directives are not strictly required but might be "nice to have":
* `@after a.mono`: This monologue should happen _after_ `a.mono`.
* Could be achieved with `@set` and `@if`
* `@in scenario`: This monologue relates to a specific random scenario.
* Could be achieved with backend setting values and `@if`
* `@bump var`: Increases a variable's value by 1.
* Requires a specific use-case.
* Causes types headaches if `var` is not an integer.
* `@event event_name`: This Monologue/Batch/Line triggers an in-game event.
* Could be achieved with `@set` and `@if`
### Monologue Templating
With variables we can template monologues like so:
```
This is a regular line of dialog
This line is templated, mentioning {{ some_event_outcome }}
```
Templated variables add an implicit constraint to that line, so the above is equivlent to:
```
This is a regular line of dialog
@require some_event_outcome
This line is templated, mentioning {{ some_event_outcome }}
```

@ -4,12 +4,16 @@
mod mono; mod mono;
use bevy::platform::hash::RandomState;
use games::*; use games::*;
use mono::*; use mono::*;
use std::hash::BuildHasher;
fn main() { fn main() {
App::new() App::new()
.add_plugins(BaseGamePlugin) .add_plugins(BaseGamePlugin {
name: "trees".into(),
})
.add_plugins(MonologueAssetsPlugin) .add_plugins(MonologueAssetsPlugin)
.add_event::<DialogEvent>() .add_event::<DialogEvent>()
.init_state::<DialogState>() .init_state::<DialogState>()
@ -29,10 +33,12 @@ fn main() {
( (
// Start a dialog if none are running // Start a dialog if none are running
start_dialog start_dialog
.run_if(in_state(DebuggingState::Off))
.run_if(in_state(DialogState::None)) .run_if(in_state(DialogState::None))
.run_if(on_event::<Pointer<Click>>), .run_if(on_event::<Pointer<Click>>),
// Close the dialog box if it is idle (not choosing) // Close the dialog box if it is idle (not choosing)
end_dialog end_dialog
.run_if(in_state(DebuggingState::Off))
.run_if(in_state(DialogState::Idle)) .run_if(in_state(DialogState::Idle))
.run_if(on_event::<Pointer<Click>>), .run_if(on_event::<Pointer<Click>>),
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>), spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
@ -309,11 +315,11 @@ fn dialog_engine(
// Fetch this monologue from the assets // Fetch this monologue from the assets
if let Some(monologue) = monologues.get(handle.clone().id()) { if let Some(monologue) = monologues.get(handle.clone().id()) {
// Fetch this batch of options // Fetch this batch of options
if let Some(options) = monologue.get(*idx) { if let Some(batch) = monologue.get(*idx) {
// Spawn the dialog options in the dialog box // Spawn the dialog options in the dialog box
options.iter().for_each(|option| { batch.lines.iter().for_each(|line| {
parent.spawn(( parent.spawn((
Text::new(option.clone()), Text::new(line.clone()),
DialogOption, DialogOption,
TextLayout::new(JustifyText::Left, LineBreak::NoWrap), TextLayout::new(JustifyText::Left, LineBreak::NoWrap),
)); ));
@ -429,11 +435,12 @@ fn dialog_box_visibility(
}; };
} }
/// Add the "script: path/to/file.mono" tooltip info
fn monologue_asset_tooltip( fn monologue_asset_tooltip(
mut over_events: EventReader<Pointer<Over>>, mut over_events: EventReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>, mut out_events: EventReader<Pointer<Out>>,
mut tooltip: ResMut<ToolTip>, mut tooltip: ResMut<ToolTip>,
scripts: Query<&TreeMonologue, With<Button>>, scripts: Query<&TreeMonologue>,
) { ) {
out_events out_events
.read() .read()
@ -514,14 +521,20 @@ fn scale_window(events: EventReader<WindowResized>, mut window: Single<&mut Wind
} }
fn delete_tree(trigger: Trigger<Pointer<Click>>, mut commands: Commands) { fn delete_tree(trigger: Trigger<Pointer<Click>>, mut commands: Commands) {
if matches!(trigger.event.button, PointerButton::Secondary) { if matches!(trigger.event.button, PointerButton::Middle) {
info!("Right Click -> Despawning {}", trigger.target()); info!("Middle Click -> Despawning {}", trigger.target());
commands.entity(trigger.target()).despawn(); commands.entity(trigger.target()).despawn();
} }
} }
fn load_monologues(server: ResMut<AssetServer>, mut loaded_folder: Local<Handle<LoadedFolder>>) { /// Load all monologues so they are in the asset store and trigger on-load events
*loaded_folder = server.load_folder("trees"); fn load_monologues(
server: ResMut<AssetServer>,
mut loaded_assets: Local<Vec<Handle<Monologue>>>,
) {
*loaded_assets = include_str!("../../../assets/trees/MONOLOGUES").split("\n").map(|path| {
server.load(path)
}).collect();
} }
fn spawn_debug_buttons( fn spawn_debug_buttons(
@ -557,6 +570,9 @@ fn spawn_debug_buttons(
.observe(toggle_debug_button_color_over) .observe(toggle_debug_button_color_over)
.observe(toggle_debug_button_color_out); .observe(toggle_debug_button_color_out);
}); });
// Spawn a tree too to make the game feel like it's doing something at startup
commands.spawn((Tree, TreeMonologue(handle.clone())));
} }
}); });
} }
@ -595,9 +611,9 @@ fn preview_monologue(
// Spawn the monologue // Spawn the monologue
let mut i = 0; let mut i = 0;
commands.entity(*container).with_children(|parent| { commands.entity(*container).with_children(|parent| {
while let Some(options) = monologue.get(i) { while let Some(batch) = monologue.get(i) {
parent.spawn((Text::new("---"), MonologuePreview)); parent.spawn((Text::new("---"), MonologuePreview));
for (n, item) in options.iter().enumerate() { for (n, item) in batch.lines.iter().enumerate() {
parent.spawn((Text::new(format!("{i}.{n}: {item}")), MonologuePreview)); parent.spawn((Text::new(format!("{i}.{n}: {item}")), MonologuePreview));
} }
// TODO: Just implement iter_batches or something // TODO: Just implement iter_batches or something
@ -624,7 +640,6 @@ fn spawn_monologue_tree(
/// TODO: This can be an `on_add` hook intead, just a little more clunky /// TODO: This can be an `on_add` hook intead, just a little more clunky
fn populate_tree( fn populate_tree(
trigger: Trigger<OnAdd, TreeMonologue>, trigger: Trigger<OnAdd, TreeMonologue>,
time: Res<Time>,
trees: Query<Entity, With<Tree>>, trees: Query<Entity, With<Tree>>,
server: Res<AssetServer>, server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
@ -632,7 +647,7 @@ fn populate_tree(
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
) { ) {
if !trees.contains(trigger.target()) { if !trees.contains(trigger.target()) {
return return;
} }
// Generate "random" X and Y Coordinates for this tree // Generate "random" X and Y Coordinates for this tree
@ -641,18 +656,19 @@ fn populate_tree(
// 3. Re-interpret as i8s // 3. Re-interpret as i8s
// 4. Cast to f32 // 4. Cast to f32
let transform = { let transform = {
let [a, b, c, d] = time.elapsed().as_secs_f32().to_be_bytes(); let n = RandomState::default().hash_one(trigger.target());
info!("Time bits: {a} {b} {c} {d}"); let [a, b, ..] = n.to_be_bytes();
let x = c as i8 / 4;
let y = d as i8 / 4; let x: f32 = a.cast_signed().wrapping_div(4).into();
let y: f32 = b.cast_signed().wrapping_div(4).into();
// Avoid mesh clipping by offsetting each on the z axis // Avoid mesh clipping by offsetting each on the z axis
let z = trees.iter().len() as f32; let z = trees.iter().len() as f32;
info!("Coordiantes: {x} {y}"); debug!("Coordiantes: {x} {y}");
Transform::from_xyz(x.into(), z, y.into()).with_scale(Vec3::splat(10.0)) Transform::from_xyz(x, z, y).with_scale(Vec3::splat(10.0))
}; };
let material = MeshMaterial3d(materials.add(StandardMaterial { let material = MeshMaterial3d(materials.add(StandardMaterial {
base_color_texture: Some(server.load("placeholder/tree.png")), base_color_texture: Some(server.load("trees/placeholder/tree.png")),
base_color: WHITE.into(), base_color: WHITE.into(),
alpha_mode: AlphaMode::Blend, alpha_mode: AlphaMode::Blend,
..default() ..default()
@ -660,12 +676,13 @@ fn populate_tree(
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))); let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
info!("Fleshing out monologuing tree"); debug!("Fleshing out monologuing tree");
commands commands
.entity(trigger.target()) .entity(trigger.target())
.insert((mesh, material, transform)) .insert((mesh, material, transform))
.observe(delete_tree); .observe(delete_tree)
.observe(drag_tree);
} }
fn hide_monologue_preview( fn hide_monologue_preview(
@ -686,3 +703,26 @@ fn hide_monologue_preview(
commands.entity(*preview).despawn_related::<Children>(); commands.entity(*preview).despawn_related::<Children>();
} }
} }
fn drag_tree(
trigger: Trigger<Pointer<Drag>>,
state: Res<State<DebuggingState>>,
mut query: Query<&mut Transform, With<Tree>>,
camera: Single<(&Camera, &GlobalTransform), With<Camera>>,
window: Single<&Window>
) {
if *state.get() == DebuggingState::On {
if let Ok(mut t) = query.get_mut(trigger.target()) {
let world_position = window
.cursor_position()
.and_then(|cursor| {
camera.0.viewport_to_world(camera.1, cursor).ok()
}).map(|ray| {
// Compute ray's distance to entity
let distance = ray.intersect_plane(t.translation, InfinitePlane3d::new(t.up())).unwrap();
ray.get_point(distance)
});
t.translation = world_position.unwrap();
}
}
}

@ -1,13 +1,64 @@
use super::*; use super::*;
/// A monologue containing a list of optional lines
#[derive(Asset, TypePath, Debug, Deserialize, Default, Clone)] #[derive(Asset, TypePath, Debug, Deserialize, Default, Clone)]
pub(crate) struct Monologue { pub(crate) struct Monologue {
value: Vec<Vec<String>>, pub batches: Vec<MonologueLineBatch>,
} }
impl Monologue { impl Monologue {
pub fn get(&self, idx: usize) -> Option<&Vec<String>> { pub fn get(&self, idx: usize) -> Option<&MonologueLineBatch> {
self.value.get(idx) self.batches.get(idx)
}
pub fn add_batch(&mut self, group: MonologueLineBatch) {
self.batches.push(group);
}
}
/// A set of possible lines in a monologue
#[derive(Debug, Deserialize, Default, Clone)]
pub(crate) struct MonologueLineBatch {
pub lines: Vec<MonologueLine>,
}
impl MonologueLineBatch {
fn add_line(&mut self, line: MonologueLine) {
self.lines.push(line);
}
}
/// A single monologue line
#[derive(Debug, Deserialize, Default, Clone)]
pub(crate) struct MonologueLine {
pub value: String,
}
impl Display for MonologueLine {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.value)
}
}
impl From<String> for MonologueLine {
fn from(value: String) -> Self {
MonologueLine {
value
}
}
}
impl Into<String> for MonologueLine {
fn into(self) -> String {
self.value
}
}
impl From<&str> for MonologueLine {
fn from(value: &str) -> Self {
MonologueLine {
value: value.into()
}
} }
} }
@ -37,25 +88,30 @@ impl AssetLoader for MonologueLoader {
let raw_string = String::from_utf8(bytes)?; let raw_string = String::from_utf8(bytes)?;
let mut value: Vec<Vec<String>> = vec![vec![]]; // Create a blank monologue to populate
let mut monologue = Monologue::default();
// Add an initial batch that may end up being empty
monologue.add_batch(MonologueLineBatch::default());
// Iterate over raw string lines in the .mono file
for line in raw_string.lines() { for line in raw_string.lines() {
// Break up into batches by --- separator
if line.starts_with("---") { if line.starts_with("---") {
value.push(Vec::new()); monologue.add_batch(MonologueLineBatch::default());
// Skip any empty lines or comments
} else if line.starts_with("#") || line.is_empty() { } else if line.starts_with("#") || line.is_empty() {
// Skip comments and blank lines // Skip comments and blank lines
// everything else we read as a monologue line
} else { } else {
value.last_mut().unwrap().push(line.into()); monologue.batches.last_mut().unwrap().add_line(line.into());
} }
} }
// Clear empty batches // Clear empty batches
value.retain(|batch| !batch.is_empty()); monologue.batches.retain(|batch| !batch.lines.is_empty());
debug!("Monologue: {:#?}", value);
let thing = Monologue { value }; Ok(monologue)
Ok(thing)
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

@ -125,6 +125,7 @@ fn toggle_debug_state(
} }
/// Simple system that enables/disables rapier debug visuals when the debugging state changes /// Simple system that enables/disables rapier debug visuals when the debugging state changes
#[cfg(not(target_arch = "wasm32"))]
fn toggle_rapier_debug_render( fn toggle_rapier_debug_render(
state: Res<State<DebuggingState>>, state: Res<State<DebuggingState>>,
mut context: ResMut<DebugRenderContext>, mut context: ResMut<DebugRenderContext>,
@ -232,10 +233,12 @@ fn hover_ui(
}); });
} }
#[cfg(not(target_arch = "wasm32"))]
fn enable_wireframe(mut wireframe_config: ResMut<WireframeConfig>) { fn enable_wireframe(mut wireframe_config: ResMut<WireframeConfig>) {
wireframe_config.global = true; wireframe_config.global = true;
} }
#[cfg(not(target_arch = "wasm32"))]
fn disable_wireframe(mut wireframe_config: ResMut<WireframeConfig>) { fn disable_wireframe(mut wireframe_config: ResMut<WireframeConfig>) {
wireframe_config.global = false; wireframe_config.global = false;
} }

@ -1,6 +1,7 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<body> <body>
<canvas id="trees-canvas"></canvas>
<script type="module"> <script type="module">
import init from './trees.js' import init from './trees.js'

Loading…
Cancel
Save