Compare commits

..

No commits in common. '73c1196a81bb07d2299ad1b47517f5f505ae466d' and '9171fee250d71107ada59f79198ec45ba098f7ec' have entirely different histories.

3
.gitignore vendored

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

1
Cargo.lock generated

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,5 +0,0 @@
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)

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

@ -0,0 +1,29 @@
# 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.

Before

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.

@ -1,9 +1,19 @@
###
# Industry
# Written by Elijah Voigt
# No copyright, it's bad on purpose
###
industry
---
cars drone in the distance with no sign of stopping...
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...
---
industry

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

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

@ -1,11 +0,0 @@
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

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

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

@ -1,13 +0,0 @@
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.

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

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

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

@ -1,5 +0,0 @@
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.

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

@ -1,3 +0,0 @@
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,14 +1,8 @@
use std::fs::File;
use std::io::Write;
fn main() {
write_version_file();
write_monologues_file();
}
fn write_version_file() {
{
use chrono::prelude::*;
use std::fs::File;
use std::io::Write;
use std::process::Command;
// Date of build
@ -42,18 +36,5 @@ fn write_version_file() {
write!(file, "0.0.0-{now}+{}-dirty", git_sha.trim())
.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.
fn main() {
App::new()
.add_plugins(BaseGamePlugin::default())
.add_plugins(BaseGamePlugin)
.init_resource::<InsideArea>()
.add_systems(
Startup,

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

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

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

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

@ -1,193 +0,0 @@
# 🌲 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,16 +4,12 @@
mod mono;
use bevy::platform::hash::RandomState;
use games::*;
use mono::*;
use std::hash::BuildHasher;
fn main() {
App::new()
.add_plugins(BaseGamePlugin {
name: "trees".into(),
})
.add_plugins(BaseGamePlugin)
.add_plugins(MonologueAssetsPlugin)
.add_event::<DialogEvent>()
.init_state::<DialogState>()
@ -33,12 +29,10 @@ fn main() {
(
// Start a dialog if none are running
start_dialog
.run_if(in_state(DebuggingState::Off))
.run_if(in_state(DialogState::None))
.run_if(on_event::<Pointer<Click>>),
// Close the dialog box if it is idle (not choosing)
end_dialog
.run_if(in_state(DebuggingState::Off))
.run_if(in_state(DialogState::Idle))
.run_if(on_event::<Pointer<Click>>),
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
@ -315,11 +309,11 @@ fn dialog_engine(
// Fetch this monologue from the assets
if let Some(monologue) = monologues.get(handle.clone().id()) {
// Fetch this batch of options
if let Some(batch) = monologue.get(*idx) {
if let Some(options) = monologue.get(*idx) {
// Spawn the dialog options in the dialog box
batch.lines.iter().for_each(|line| {
options.iter().for_each(|option| {
parent.spawn((
Text::new(line.clone()),
Text::new(option.clone()),
DialogOption,
TextLayout::new(JustifyText::Left, LineBreak::NoWrap),
));
@ -435,12 +429,11 @@ fn dialog_box_visibility(
};
}
/// Add the "script: path/to/file.mono" tooltip info
fn monologue_asset_tooltip(
mut over_events: EventReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>,
mut tooltip: ResMut<ToolTip>,
scripts: Query<&TreeMonologue>,
scripts: Query<&TreeMonologue, With<Button>>,
) {
out_events
.read()
@ -521,20 +514,14 @@ fn scale_window(events: EventReader<WindowResized>, mut window: Single<&mut Wind
}
fn delete_tree(trigger: Trigger<Pointer<Click>>, mut commands: Commands) {
if matches!(trigger.event.button, PointerButton::Middle) {
info!("Middle Click -> Despawning {}", trigger.target());
if matches!(trigger.event.button, PointerButton::Secondary) {
info!("Right Click -> Despawning {}", trigger.target());
commands.entity(trigger.target()).despawn();
}
}
/// Load all monologues so they are in the asset store and trigger on-load events
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 load_monologues(server: ResMut<AssetServer>, mut loaded_folder: Local<Handle<LoadedFolder>>) {
*loaded_folder = server.load_folder("trees");
}
fn spawn_debug_buttons(
@ -570,9 +557,6 @@ fn spawn_debug_buttons(
.observe(toggle_debug_button_color_over)
.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())));
}
});
}
@ -611,9 +595,9 @@ fn preview_monologue(
// Spawn the monologue
let mut i = 0;
commands.entity(*container).with_children(|parent| {
while let Some(batch) = monologue.get(i) {
while let Some(options) = monologue.get(i) {
parent.spawn((Text::new("---"), MonologuePreview));
for (n, item) in batch.lines.iter().enumerate() {
for (n, item) in options.iter().enumerate() {
parent.spawn((Text::new(format!("{i}.{n}: {item}")), MonologuePreview));
}
// TODO: Just implement iter_batches or something
@ -640,6 +624,7 @@ fn spawn_monologue_tree(
/// TODO: This can be an `on_add` hook intead, just a little more clunky
fn populate_tree(
trigger: Trigger<OnAdd, TreeMonologue>,
time: Res<Time>,
trees: Query<Entity, With<Tree>>,
server: Res<AssetServer>,
mut commands: Commands,
@ -647,7 +632,7 @@ fn populate_tree(
mut materials: ResMut<Assets<StandardMaterial>>,
) {
if !trees.contains(trigger.target()) {
return;
return
}
// Generate "random" X and Y Coordinates for this tree
@ -656,19 +641,18 @@ fn populate_tree(
// 3. Re-interpret as i8s
// 4. Cast to f32
let transform = {
let n = RandomState::default().hash_one(trigger.target());
let [a, b, ..] = n.to_be_bytes();
let x: f32 = a.cast_signed().wrapping_div(4).into();
let y: f32 = b.cast_signed().wrapping_div(4).into();
let [a, b, c, d] = time.elapsed().as_secs_f32().to_be_bytes();
info!("Time bits: {a} {b} {c} {d}");
let x = c as i8 / 4;
let y = d as i8 / 4;
// Avoid mesh clipping by offsetting each on the z axis
let z = trees.iter().len() as f32;
debug!("Coordiantes: {x} {y}");
Transform::from_xyz(x, z, y).with_scale(Vec3::splat(10.0))
info!("Coordiantes: {x} {y}");
Transform::from_xyz(x.into(), z, y.into()).with_scale(Vec3::splat(10.0))
};
let material = MeshMaterial3d(materials.add(StandardMaterial {
base_color_texture: Some(server.load("trees/placeholder/tree.png")),
base_color_texture: Some(server.load("placeholder/tree.png")),
base_color: WHITE.into(),
alpha_mode: AlphaMode::Blend,
..default()
@ -676,13 +660,12 @@ fn populate_tree(
let mesh = Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0))));
debug!("Fleshing out monologuing tree");
info!("Fleshing out monologuing tree");
commands
.entity(trigger.target())
.insert((mesh, material, transform))
.observe(delete_tree)
.observe(drag_tree);
.observe(delete_tree);
}
fn hide_monologue_preview(
@ -703,26 +686,3 @@ fn hide_monologue_preview(
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,64 +1,13 @@
use super::*;
/// A monologue containing a list of optional lines
#[derive(Asset, TypePath, Debug, Deserialize, Default, Clone)]
pub(crate) struct Monologue {
pub batches: Vec<MonologueLineBatch>,
value: Vec<Vec<String>>,
}
impl Monologue {
pub fn get(&self, idx: usize) -> Option<&MonologueLineBatch> {
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()
}
pub fn get(&self, idx: usize) -> Option<&Vec<String>> {
self.value.get(idx)
}
}
@ -88,30 +37,25 @@ impl AssetLoader for MonologueLoader {
let raw_string = String::from_utf8(bytes)?;
// 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());
let mut value: Vec<Vec<String>> = vec![vec![]];
// Iterate over raw string lines in the .mono file
for line in raw_string.lines() {
// Break up into batches by --- separator
if line.starts_with("---") {
monologue.add_batch(MonologueLineBatch::default());
// Skip any empty lines or comments
value.push(Vec::new());
} else if line.starts_with("#") || line.is_empty() {
// Skip comments and blank lines
// everything else we read as a monologue line
} else {
monologue.batches.last_mut().unwrap().add_line(line.into());
value.last_mut().unwrap().push(line.into());
}
}
// Clear empty batches
monologue.batches.retain(|batch| !batch.lines.is_empty());
value.retain(|batch| !batch.is_empty());
debug!("Monologue: {:#?}", value);
Ok(monologue)
let thing = Monologue { value };
Ok(thing)
}
fn extensions(&self) -> &[&str] {

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

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

Loading…
Cancel
Save