WIP scanning, analysis and AST todo
This commit is contained in:
parent
dc6b360583
commit
38eab409c6
@ -1,4 +1,61 @@
|
||||
[Duct Tape]
|
||||
d:The tape that does it all.
|
||||
DuctTape:
|
||||
str="Duct Tape"
|
||||
status=[.Full, .Empty]
|
||||
location=Warehouse
|
||||
|
||||
[Flash Light]
|
||||
FlashLight:
|
||||
str="Flash Light"
|
||||
status=[.Bright, .Dead, .Broken]
|
||||
_onExamine:
|
||||
if $self.status = .Bright:
|
||||
A handy tool for seeing your way out.
|
||||
else if $self.status = .Dead:
|
||||
A handy tool for seeing your way out, unfortunately its out of batteries.
|
||||
else:
|
||||
A handy tool for seeing your way out, unfortunately its busted.
|
||||
|
||||
_onUse:
|
||||
if $context.args.contains(Batteries) and $self.status = .Dead:
|
||||
$self.status = .Bright
|
||||
$player.inventory.remove(Batteries)
|
||||
|
||||
You screw the back on tight, and the light comes on!
|
||||
_onFix:
|
||||
if $context.args.contains(DuctTape) and $self.status = .Broken:
|
||||
$self.status = .Dead
|
||||
$DuctTape.status = .Empty
|
||||
|
||||
Nothing duct tape cant fix! The flashlight just needs some power.
|
||||
|
||||
Batteries:
|
||||
_onExamine:
|
||||
Energized little cylinders, good for something...
|
||||
|
||||
# Hammer:
|
||||
|
||||
# [Hammer]
|
||||
# desc= Good for breaking things
|
||||
# status=Solid
|
||||
# rel=Flash Light=>Broken
|
||||
|
||||
# [Key1]
|
||||
# name= Key
|
||||
# desc= A mysterious key
|
||||
|
||||
# [Key2]
|
||||
# name= Key
|
||||
# desc= A different key, not to be confused with the other
|
||||
|
||||
# [Locked Chest]
|
||||
# decs= A large chest with two key holes
|
||||
# status=Locked,Closed,Opened
|
||||
# *Open=
|
||||
# &Locked::Its locked
|
||||
# &Opened::Its already open
|
||||
# &:s.Opened:The large chest swings open.
|
||||
# *Close=
|
||||
# &Locked::[Its locked, and therefore closed. You arent strong enough to break it with your hands.<Hammer.s=&Solid>You cant break it open, but maybe with something tough enough...]
|
||||
# &Closed::Its already closed!!
|
||||
# &Opened:s.Closed:The chest closes with a clunk.
|
||||
# *Unlock=
|
||||
# &
|
||||
|
||||
161
cyo_spec.md
Normal file
161
cyo_spec.md
Normal file
@ -0,0 +1,161 @@
|
||||
# Mindset
|
||||
|
||||
In the world of text adventures, at any given moment, a player:
|
||||
- Resides in a specific location
|
||||
- Can issue any arbitrary command
|
||||
- Said command should never cause an error
|
||||
- Posseses an inventory with any number of objects
|
||||
-
|
||||
|
||||
All entities in a text adventure world are typically broken into two categories: scenes and objects.
|
||||
But, I believe there is good reason to abstract all entities into just objects.
|
||||
|
||||
Then, objects are duck-typed into specific things by way of their available methods.
|
||||
Methods include but are not limited to:
|
||||
- `_onEnter`, used for scenes, to read out text of the location
|
||||
- `_onExit`, used for scenes, to direct the player to the next destination
|
||||
- `_onAcquire`, used for objects that are meant to be kept
|
||||
- `_onInpect`, used for any object when the player wishes to know more
|
||||
|
||||
A graph markdown was deemed insufficient for cyano, as my stories are not going to fit cleanly along
|
||||
some API. What I really will need to be able to do is code it, as any good engine of any kind.
|
||||
|
||||
Goals for the language:
|
||||
|
||||
## 1. Reading as First Class
|
||||
A story is only worth what its words convey, and you only have a good story if you can read it.
|
||||
|
||||
Imagine a file whose contents read:
|
||||
```
|
||||
func goToWindow:
|
||||
print("You approach the window and its clear you could survive the jump");
|
||||
input = get_input()
|
||||
if input == "jump":
|
||||
print("You fall gracefully to the ground")
|
||||
else if
|
||||
...
|
||||
```
|
||||
|
||||
It honestly would be just as well to scrap this project and write a one-off in python.
|
||||
The purpose of writing an entire engine would be to pull away all of the unnecessary bits and bytes
|
||||
of sequencing, IO, object relations and requirements and let the writer focus on the actual writing.
|
||||
|
||||
Instead picture a file like this:
|
||||
```
|
||||
_onEnter:
|
||||
You approach the window and its clear you could survive the jump
|
||||
|
||||
_onJump:
|
||||
You fall gracefully to the ground
|
||||
```
|
||||
|
||||
This is still the outset, so more syntax may be required, but the goal would be to make everything
|
||||
else get out of the way, and reading your story takes the front seat.
|
||||
|
||||
## 2a. Arbitrary Functionality
|
||||
A story can take us anywhere. So why would the engine that powers it only allow objects to have one of up to
|
||||
a maximum of three conditions (Opened, Closed, Locked).
|
||||
|
||||
In reality, a chest could be Opened, Broken, Dusty, Sentient and who knows what else. The goal would be to allow
|
||||
the writer to have utmost freedom in writing the story they want to tell.
|
||||
|
||||
## 2b. Safe Operation
|
||||
However, we dont want a dynamic story to have dead ends, missing links, or endless loops. When a chest depends on
|
||||
a key that doesnt exist? Thats no good. We want as many conditions as possible to be checked as soon as possible
|
||||
in the writing process to help make the greatest story that has yet to be experienced.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Cyo Spec
|
||||
|
||||
## Summary
|
||||
|
||||
Cyano's underlying framework is using callbacks. You can think of it as defining a series of triggers and effects.
|
||||
Essentially, nothing happens without the user doing something, so we dont need the cyano engine to "run" code.
|
||||
Instead, it should read the user input, figure out what they mean, then lookup the associated trigger that the user is tripping.
|
||||
|
||||
Triggers could be anything:
|
||||
- Entering a room
|
||||
- Combining things in their inventory
|
||||
- Putting a monkey idol on a pillar
|
||||
- Reaching zero health
|
||||
|
||||
So, with these two concepts: entities and callbacks (triggers), we can create any story with any sort of form.
|
||||
|
||||
Want to make it RPG? Create a player entity with stats, and create triggers like `_onKillMob`, `_onLevelUp`.
|
||||
Traditional text escape adventure? Chest entity with `_onUnlock`, `_onOpen`.
|
||||
|
||||
## Nitty Gritty
|
||||
|
||||
### Entities
|
||||
Defining Entities:
|
||||
- A file can define multiple entities only if these definitions are qualified.
|
||||
- A file cannot have unqualified and qualified definitions.
|
||||
- A file can only define one set of unqualified definitions and no others.
|
||||
- Entity identifiers are case-insensitive, and can only contain letters, numbers, and underscores.
|
||||
|
||||
Qualified Definition:
|
||||
```cyo
|
||||
scenes.cyo
|
||||
|
||||
_onEnter_LivingRoom:
|
||||
_onLeave_LivingRoom:
|
||||
_onExplode_LivingRoom:
|
||||
```
|
||||
|
||||
Unqualified Definition:
|
||||
```cyo
|
||||
living_room.cyo
|
||||
|
||||
_onEnter:
|
||||
_onLeave:
|
||||
_onExplode:
|
||||
```
|
||||
|
||||
Both would create internally a LivingRoom entity.
|
||||
In the case of an unqualified definition, the basename of the file is used as the entities identifier. Underscores in
|
||||
the filename will be removed. If an underscore is desired in the identifier, the filename would need two:
|
||||
`living__room.cyo`
|
||||
|
||||
### Global State
|
||||
Global state is accessible within any callback with said object properties TBD but would look like:
|
||||
```cyo
|
||||
|
||||
_onRead:
|
||||
if $player.wearingPendant:
|
||||
The book's text comes to life!
|
||||
|
||||
else:
|
||||
The book's dull pages seem ready to fall apart with age.
|
||||
```
|
||||
|
||||
Attributes that would be handy for the global state to hold:
|
||||
- Inventory
|
||||
- Current location
|
||||
- Recent action(s)
|
||||
- Arbitrary stats (author defined)
|
||||
|
||||
Should these all reside in the `$player` object? This will probably evolve as need arises.
|
||||
|
||||
### Entity State
|
||||
You want to open a door, but only if the pressure plate in the room is weighed down with something. Entities need to be
|
||||
able to define and track arbitrary internal state. This might look something like:
|
||||
```cyo
|
||||
PressurePlate:
|
||||
pressed=false
|
||||
|
||||
_onInspect_PressurePlate:
|
||||
if $pressed:
|
||||
"Its depressed, now what?"
|
||||
else if $player.:
|
||||
"Its barely noticable on the dusty stone floor, but you can just make out the lip of the plate. Something heavy could depress it."
|
||||
|
||||
```
|
||||
|
||||
### Callback Rules
|
||||
@ -1,7 +1,29 @@
|
||||
const std = @import("std");
|
||||
const CyoContent = @import("../cyo/cyo.zig").content;
|
||||
|
||||
const DEFAULT_CYO_SOURCE_PATH = "cyo";
|
||||
const DEFAULT_CYO_SOURCE_PATH = "./cyo";
|
||||
|
||||
const Token = struct { line: u32, pos: u32, lexeme: Lexeme, contents: []const u8 };
|
||||
|
||||
const Lexeme = enum {
|
||||
Lt, // <
|
||||
Gt, // >
|
||||
LeftParen, // (
|
||||
RightParen, // )
|
||||
LeftBracket, // [
|
||||
RightBracket, // ]
|
||||
Text, // Foo bar blah. Another.
|
||||
Dollar, // $
|
||||
Period, // .
|
||||
Colon, // :
|
||||
Equals, // =
|
||||
Hashtag, // #
|
||||
Underscore, // _
|
||||
Newline, // \n
|
||||
Tab, // \t
|
||||
Space, // ' '
|
||||
Eof, //
|
||||
};
|
||||
|
||||
pub const CyoError = error{ BadSource, BadIter };
|
||||
|
||||
@ -38,21 +60,22 @@ pub const CyoParser = struct {
|
||||
var files_contents = std.StringHashMap([]const u8).init(allocator);
|
||||
try walkDirs(allocator, cyo_dir, 0, &files_contents);
|
||||
|
||||
var iter = files_contents.keyIterator();
|
||||
var files_tokens = try lexCyoFiles(allocator, files_contents);
|
||||
printFilesTokens(files_tokens);
|
||||
|
||||
var iter = files_tokens.keyIterator();
|
||||
while (iter.next()) |key| {
|
||||
const content = files_contents.get(key.*);
|
||||
if (content) |c| {
|
||||
std.debug.print("Got key: {s}\nContent:{s}\n\n", .{ key.*, c });
|
||||
} else {
|
||||
std.debug.print("Got key: {s}\nContent empty\n\n", .{key.*});
|
||||
const tokens = files_tokens.get(key.*);
|
||||
if (tokens) |t| {
|
||||
t.deinit();
|
||||
}
|
||||
allocator.free(key.*);
|
||||
}
|
||||
|
||||
// var path_buf: [128]u8 = undefined;
|
||||
|
||||
files_tokens.deinit();
|
||||
// const cyo_dir_path = cyo_dir.realpath(pathname: []const u8, out_buffer: []u8)
|
||||
|
||||
// 2. process files
|
||||
|
||||
// 2a. lexical - validate file syntax
|
||||
// 2b. syntactic parsing
|
||||
// 2c. semantic - create objects and scenes
|
||||
@ -61,6 +84,7 @@ pub const CyoParser = struct {
|
||||
return CyoContent{ .allocator = allocator, .files_contents = files_contents };
|
||||
}
|
||||
|
||||
// Recursively walks through directory to get all .cyo files
|
||||
fn walkDirs(allocator: std.mem.Allocator, cyo_dir: std.fs.Dir, depth: u8, files_contents: *std.StringHashMap([]const u8)) !void {
|
||||
var cyo_iter = cyo_dir.iterate();
|
||||
while (cyo_iter.next() catch |err| {
|
||||
@ -69,11 +93,15 @@ pub const CyoParser = struct {
|
||||
}) |cyo_entry| {
|
||||
switch (cyo_entry.kind) {
|
||||
.file => {
|
||||
// std.fs.cyo_entry.name;
|
||||
// only grab `.cyo` files
|
||||
if (cyo_entry.name.len <= 3 or !std.mem.eql(u8, ".cyo", cyo_entry.name[cyo_entry.name.len - 4 .. cyo_entry.name.len])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (0..depth) |_| {
|
||||
std.debug.print("\t", .{});
|
||||
}
|
||||
std.debug.print("- File: {s}\n", .{cyo_entry.name});
|
||||
// std.debug.print("- File: {s}\n", .{cyo_entry.name});
|
||||
const file_path = try cyo_dir.realpathAlloc(allocator, cyo_entry.name);
|
||||
var cyo_file = try std.fs.openFileAbsolute(file_path, .{ .mode = .read_only });
|
||||
|
||||
@ -84,7 +112,7 @@ pub const CyoParser = struct {
|
||||
for (0..depth) |_| {
|
||||
std.debug.print("\t", .{});
|
||||
}
|
||||
std.debug.print("Dir: {s}\n", .{cyo_entry.name});
|
||||
// std.debug.print("Dir: {s}\n", .{cyo_entry.name});
|
||||
const dir_path = try cyo_dir.realpathAlloc(allocator, cyo_entry.name);
|
||||
defer allocator.free(dir_path);
|
||||
|
||||
@ -92,18 +120,220 @@ pub const CyoParser = struct {
|
||||
try walkDirs(allocator, new_cyo_dir, depth + 1, files_contents);
|
||||
},
|
||||
else => {
|
||||
std.debug.print("ignoring other types...", .{});
|
||||
// std.debug.print("ignoring other types...", .{});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn printFilesContents(files_contents: std.StringHashMap([]const u8)) void {
|
||||
var iter = files_contents.keyIterator();
|
||||
while (iter.next()) |key| {
|
||||
const content = files_contents.get(key.*);
|
||||
if (content) |c| {
|
||||
std.debug.print("Got key: {s}\nContent:{s}\n\n", .{ key.*, c });
|
||||
} else {
|
||||
std.debug.print("Got key: {s}\nContent empty\n\n", .{key.*});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lexCyoFiles(allocator: std.mem.Allocator, files_contents: std.StringHashMap([]const u8)) !std.StringHashMap(std.ArrayList(Token)) {
|
||||
var files_tokens = std.StringHashMap(std.ArrayList(Token)).init(allocator);
|
||||
errdefer {
|
||||
var iter = files_tokens.keyIterator();
|
||||
while (iter.next()) |key| {
|
||||
const tokens = files_tokens.get(key.*);
|
||||
if (tokens) |t| {
|
||||
t.deinit();
|
||||
}
|
||||
allocator.free(key.*);
|
||||
}
|
||||
files_tokens.deinit();
|
||||
}
|
||||
|
||||
var iter = files_contents.keyIterator();
|
||||
while (iter.next()) |key| {
|
||||
const content = files_contents.get(key.*);
|
||||
if (content == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var tokens = std.ArrayList(Token).init(allocator);
|
||||
const c = content.?;
|
||||
var i: u32 = 0;
|
||||
var line: u32 = 1;
|
||||
var col: u32 = 1;
|
||||
while (i < c.len) {
|
||||
const lexeme = charToLexeme(c[i]);
|
||||
switch (lexeme) {
|
||||
// whitespace
|
||||
.Space, .Tab => {
|
||||
const char_repeats = greedyCapture(c, i);
|
||||
for (0..char_repeats) |_| {
|
||||
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + char_repeats] });
|
||||
col += 1;
|
||||
}
|
||||
i += char_repeats;
|
||||
},
|
||||
.Newline => {
|
||||
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + 1] });
|
||||
line += 1;
|
||||
col = 1;
|
||||
i += 1;
|
||||
},
|
||||
// symbols
|
||||
.Gt,
|
||||
.Lt,
|
||||
.LeftParen,
|
||||
.RightParen,
|
||||
.LeftBracket,
|
||||
.RightBracket,
|
||||
.Dollar,
|
||||
.Period,
|
||||
.Equals,
|
||||
.Colon,
|
||||
.Underscore,
|
||||
=> {
|
||||
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + 1] });
|
||||
// check length of content is one
|
||||
std.debug.assert(tokens.items[tokens.items.len - 1].contents.len == 1);
|
||||
|
||||
col += 1;
|
||||
i += 1;
|
||||
},
|
||||
// text
|
||||
.Text => {
|
||||
const text_length = captureText(c, i);
|
||||
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + text_length] });
|
||||
|
||||
col += text_length;
|
||||
i += text_length;
|
||||
},
|
||||
.Hashtag => {
|
||||
const to_end_of_line = captureLine(c, i);
|
||||
// TODO for testing, remove as we dont need to save comments
|
||||
try tokens.append(.{ .line = line, .pos = col, .lexeme = lexeme, .contents = c[i .. i + to_end_of_line] });
|
||||
|
||||
col += to_end_of_line;
|
||||
i += to_end_of_line;
|
||||
},
|
||||
.Eof => unreachable,
|
||||
}
|
||||
}
|
||||
// Add eof token
|
||||
try tokens.append(.{ .line = line, .pos = col, .lexeme = Lexeme.Eof, .contents = "" });
|
||||
|
||||
// Add tokens to hashmap
|
||||
const key_copy = try allocator.alloc(u8, key.len);
|
||||
std.mem.copyForwards(u8, key_copy, key.*);
|
||||
try files_tokens.put(key_copy, tokens);
|
||||
}
|
||||
return files_tokens;
|
||||
}
|
||||
|
||||
fn printFilesTokens(files_tokens: std.StringHashMap(std.ArrayList(Token))) void {
|
||||
var iter = files_tokens.keyIterator();
|
||||
while (iter.next()) |key| {
|
||||
std.debug.print("File: {s}", .{key.*});
|
||||
const tokens = files_tokens.get(key.*);
|
||||
if (tokens) |ts| {
|
||||
for (ts.items) |token| {
|
||||
std.debug.print("\tGot Token: {s}\tCol{d}:L{d}\t{s}\n", .{ @tagName(token.lexeme), token.pos, token.line, token.contents });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn charToLexeme(char: u8) Lexeme {
|
||||
switch (char) {
|
||||
' ' => {
|
||||
return .Space;
|
||||
},
|
||||
'\t' => {
|
||||
return .Tab;
|
||||
},
|
||||
'\n' => {
|
||||
return .Newline;
|
||||
},
|
||||
'<' => {
|
||||
return .Lt;
|
||||
},
|
||||
'>' => {
|
||||
return .Gt;
|
||||
},
|
||||
'(' => {
|
||||
return .LeftParen;
|
||||
},
|
||||
')' => {
|
||||
return .RightParen;
|
||||
},
|
||||
'[' => {
|
||||
return .LeftBracket;
|
||||
},
|
||||
']' => {
|
||||
return .RightBracket;
|
||||
},
|
||||
'$' => {
|
||||
return .Dollar;
|
||||
},
|
||||
'.' => {
|
||||
return .Period;
|
||||
},
|
||||
'=' => {
|
||||
return .Equals;
|
||||
},
|
||||
':' => {
|
||||
return .Colon;
|
||||
},
|
||||
'_' => {
|
||||
return .Underscore;
|
||||
},
|
||||
'#' => {
|
||||
return .Hashtag;
|
||||
},
|
||||
else => return .Text,
|
||||
}
|
||||
}
|
||||
|
||||
// given a char, it returns the number of bytes that char repeats forward starting at `i`
|
||||
fn greedyCapture(seq: []const u8, i: u32) u32 {
|
||||
const cap_char = seq[i];
|
||||
var j = i;
|
||||
while (j < seq.len and seq[j] == cap_char) {
|
||||
j += 1;
|
||||
}
|
||||
return j - i;
|
||||
}
|
||||
|
||||
fn captureText(seq: []const u8, i: u32) u32 {
|
||||
var j = i;
|
||||
while (j < seq.len and charToLexeme(seq[j]) == .Text) {
|
||||
j += 1;
|
||||
}
|
||||
return j - i;
|
||||
}
|
||||
|
||||
fn captureLine(seq: []const u8, i: u32) u32 {
|
||||
var j = i;
|
||||
while (j < seq.len) {
|
||||
const lex = charToLexeme(seq[j]);
|
||||
if (lex == .Newline or lex == .Eof) {
|
||||
break;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
return j - i;
|
||||
}
|
||||
};
|
||||
|
||||
test "parse test" {
|
||||
// TODO, programatically create a test directory instead of relying on the test directory to have the right contents
|
||||
const cyo_test_dir_path = try std.fs.cwd().realpathAlloc(std.testing.allocator, "./test/cyo_test_dir");
|
||||
defer std.testing.allocator.free(cyo_test_dir_path);
|
||||
var cyo_parser = try CyoParser.init(std.testing.allocator, cyo_test_dir_path);
|
||||
defer cyo_parser.deinit();
|
||||
|
||||
try std.testing.expectEqual(6, cyo_parser.cyo_content.files_contents.count());
|
||||
// Verify reading in of correct files
|
||||
try std.testing.expectEqual(7, cyo_parser.cyo_content.files_contents.count());
|
||||
}
|
||||
|
||||
1
test/cyo_test_dir/actions.cyo
Normal file
1
test/cyo_test_dir/actions.cyo
Normal file
@ -0,0 +1 @@
|
||||
|
||||
@ -1,4 +1,32 @@
|
||||
[Duct Tape]
|
||||
d:The tape that does it all.
|
||||
DuctTape:
|
||||
str="Duct Tape"
|
||||
status=[.Full, .Empty]
|
||||
location=Warehouse
|
||||
|
||||
[Flash Light]
|
||||
FlashLight:
|
||||
str="Flash Light"
|
||||
status=[.Bright, .Dead, .Broken]
|
||||
_onExamine:
|
||||
if $self.status = .Bright:
|
||||
A handy tool for seeing your way out.
|
||||
else if $self.status = .Dead:
|
||||
A handy tool for seeing your way out, unfortunately its out of batteries.
|
||||
else:
|
||||
A handy tool for seeing your way out, unfortunately its busted.
|
||||
|
||||
_onUse:
|
||||
if $context.args.contains(Batteries) and $self.status = .Dead:
|
||||
$self.status = .Bright
|
||||
$player.inventory.remove(Batteries)
|
||||
|
||||
You screw the back on tight, and the light comes on!
|
||||
_onFix:
|
||||
if $context.args.contains(DuctTape) and $self.status = .Broken:
|
||||
$self.status = .Dead
|
||||
$DuctTape.status = .Empty
|
||||
|
||||
Nothing duct tape cant fix! The flashlight just needs some power.
|
||||
|
||||
Batteries:
|
||||
_onExamine:
|
||||
Energized little cylinders, good for something...
|
||||
|
||||
@ -1,17 +1,13 @@
|
||||
[Warehouse]
|
||||
Warehouse:
|
||||
_onEnter:
|
||||
Your vision blurs as you fumble for the tablet. The glow of your fingers is nearly gone. The salty tablet is the last
|
||||
sensation before everything goes dark.
|
||||
<pause3>
|
||||
<clear>
|
||||
You awaken. How long has it been? Thanks to your dull glow, you can read the clock on the wall. 8:36. Only a few hours.
|
||||
Thank God you brought one with you. <p1> But its not a cure, just a band-aid. <p4> Time is ticking. <p1> You've got 24 hours
|
||||
to breathe, better get back before then.
|
||||
|
||||
Your vision blurs as you fumble for the tablet. The glow of your fingers is nearly gone. The salty tablet is the last
|
||||
sensation before everything goes dark.
|
||||
|
||||
---
|
||||
|
||||
You awaken. How long has it been? Thanks to your dull glow, you can read the clock on the wall. 8:36. Only a few hours.
|
||||
Thank God you brought one with you. <p1> But its not a cure, just a band-aid. <p4> Time is ticking. <p1> You've got 24 hours
|
||||
to breathe, better get back before then.
|
||||
|
||||
[Warehouse.d]
|
||||
It might have been an Amazon warehouse. Everything seems sacked. Empty shelves twist in some corporate labrynth before you.
|
||||
Would have been nice to have something to show for this trip. But information isn't useless.
|
||||
|
||||
[Warehouse.i]
|
||||
Duct Tape
|
||||
_onExamine:
|
||||
It might have been an Amazon warehouse. Everything seems sacked. Empty shelves twist in some corporate labrynth before you.
|
||||
Would have been nice to have something to show for this trip. But information isn't useless.
|
||||
|
||||
@ -1 +1,25 @@
|
||||
[Bathroom]
|
||||
Bathroom:
|
||||
washed=false
|
||||
|
||||
_onEnter:
|
||||
Its a normal bathroom.
|
||||
if not $washed:
|
||||
Should probably wash up.
|
||||
|
||||
_onLook:
|
||||
Bathroom complete with sink, faucet, the whole lot.
|
||||
|
||||
_onWash:
|
||||
$washed=true
|
||||
Nice cold water.
|
||||
|
||||
_beforeOnWash:
|
||||
if $washed:
|
||||
You dont need to wash again.
|
||||
_block
|
||||
|
||||
_afterOnWash:
|
||||
if not $previous.bathroom.washed:
|
||||
You feel refreshed.
|
||||
<pause2>
|
||||
Time to get to work.
|
||||
|
||||
1
test/cyo_test_dir/scenes/bathroom.foo
Normal file
1
test/cyo_test_dir/scenes/bathroom.foo
Normal file
@ -0,0 +1 @@
|
||||
[Bathroom]
|
||||
Loading…
Reference in New Issue
Block a user