├── .gitignore
├── LICENSE
├── README.md
├── design.css
├── dist
└── format.js
├── docs
├── .gitignore
├── CNAME
├── README.md
├── _config.yml
├── _includes
│ ├── disqus.html
│ ├── footer.html
│ ├── google_analytics.html
│ ├── header.html
│ └── navigation.html
├── _layouts
│ ├── default.html
│ └── page.html
├── _posts
│ ├── .gitkeep
│ ├── 2017-08-07-basic-items.md
│ ├── 2017-08-07-changelog.md
│ ├── 2017-08-07-choose-the-name.md
│ ├── 2017-08-07-examples.md
│ ├── 2017-08-07-health-and-gold.md
│ ├── 2017-08-07-installing.md
│ ├── 2017-08-07-item-chooser.md
│ ├── 2017-08-07-items.md
│ └── 2017-08-07-javascript-basics.md
├── assets
│ ├── home-demo.gif
│ └── items
│ │ ├── weapon_01.png
│ │ ├── weapon_02.png
│ │ ├── weapon_21.png
│ │ ├── weapon_22.png
│ │ ├── weapon_221.png
│ │ ├── weapon_222.png
│ │ ├── weapon_61.png
│ │ └── weapon_62.png
├── bin
│ └── jekyll-page
├── css
│ ├── main.css
│ └── syntax.css
└── index.md
├── examples
└── complete-tutorial.html
├── images
└── .gitignore
├── index.html
├── package-lock.json
├── package.json
├── scripts
└── item-table-generator.ts
├── src
├── Character.ts
├── CharacterComponent.tsx
├── Choice.ts
├── Interface.tsx
├── Inventory.ts
├── Item.ts
├── ItemComponent.tsx
├── ItemDragDataTransfer.ts
├── Passage.ts
├── Shop.ts
├── Stat.ts
├── Story.ts
├── StoryConfig.ts
├── Tooltip.tsx
├── defaultItems.ts
├── defaultStoryConfig.ts
├── helper.ts
└── script.tsx
├── tsconfig.json
├── webpack.config.js
└── webpack
└── TwineFormatPlugin.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | npm-debug.log*
3 | .vscode/
4 | /dist/*
5 | !/dist/format.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Longwelwind
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Adventures
2 |
3 | Adventures is a custom story format for [Twine 2](https://twinery.org) that allows writers to make interactive stories with RPG elements such as health, items, inventory, gold and more.
4 |
5 | ## Usage
6 |
7 | Information about how installing and creating stories in Adventures is [available here](http://longwelwind.github.io/adventures).
8 |
9 | ## Development
10 |
11 | If you wish modify Adventures, you can clone the repository and install the dependencies using:
12 |
13 | ```
14 | npm install
15 | ```
16 |
17 | ### Assets
18 |
19 | Adventures uses [7Soul's excellent but-not-free assets](https://7soul.itch.io/). Because I obviously can't redistribute them freely, they aren't included in the repository if you clone it. You will need to buy them if you want to build Adventures. You will need at least:
20 |
21 | * [7Soul's RPG Graphics - Icons](https://7soul.itch.io/7souls-rpg-graphics-pack-1-icons)
22 | * [7Soul's RPG Graphics - UI](https://7soul.itch.io/7souls-rpg-graphics-pack-2-ui)
23 |
24 | Once you obtained them, you need to unzip the archive files into the `images/` folder. Once done, you should have a folder directory that roughly lookes like this:
25 |
26 | ```
27 | adventures/
28 | ├─ ...
29 | ├─ images/
30 | | ├─ Arrows/
31 | | ├─ Bars/
32 | | ├─ Buttons/
33 | | ├─ ...
34 | | ├─ Pack 1B/
35 | | ├─ Pack 1B-Renamed/
36 | | └─ ...
37 | ├─ ...
38 | ```
39 |
40 | ### Debug & Build
41 |
42 | If you want modify Adventures, you will want to build & debug it locally:
43 |
44 | * First launch Webpack using the command:
45 |
46 | ```
47 | npm run start
48 | ```
49 |
50 | * In Twine, add the custom story format by clicking `Formats`, then `Add a New Format` and then past the file address to `format.js` which should look like:
51 | ```
52 | (On Windows)
53 | file:///E:/Users/.../adventures/dist/format.js
54 | (On Unix)
55 | file:///home/.../adventures/dist/format.js
56 | ```
57 |
58 | With this setup, you don't need to re-add the custom story format everytime you make a modification in the code. Twine will automatically take the newest version of `format.js` everytime you launch your story.
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw?
2 | _site
3 | _pages
4 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | adventures.longwelwind.net
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | [](https://bitdeli.com/free "Bitdeli Badge")
2 |
3 | Read the docs: http://bruth.github.io/jekyll-docs-template
4 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | # Site title and subtitle. This is used in _includes/header.html
2 | title: 'Adventures'
3 | subtitle: 'Create Twine stories with RPG elements'
4 |
5 | # if you wish to integrate disqus on pages set your shortname here
6 | disqus_shortname: ''
7 |
8 | # if you use google analytics, add your tracking id here
9 | google_analytics_id: ''
10 |
11 | # Enable/show navigation. There are there options:
12 | # 0 - always hide
13 | # 1 - always show
14 | # 2 - show only if posts are present
15 | navigation: 1
16 |
17 | # URL to source code, used in _includes/footer.html
18 | codeurl: 'https://github.com/bruth/jekyll-docs-template'
19 |
20 | # Default categories (in order) to appear in the navigation
21 | sections: [
22 | ['basic-usage', 'Basic Usage'],
23 | ['advanced-usage', 'Advanced Usage'],
24 | ['tut', 'Tutorial'],
25 | ['ref', 'Reference'],
26 | ['dev', 'Developers'],
27 | ['others', 'Others']
28 | ]
29 |
30 | # Keep as an empty string if served up at the root. If served up at a specific
31 | # path (e.g. on GitHub pages) leave off the trailing slash, e.g. /my-project
32 | baseurl: ''
33 |
34 | # Dates are not included in permalinks
35 | permalink: none
36 |
37 | # Syntax highlighting
38 | highlighter: rouge
39 |
40 | # Since these are pages, it doesn't really matter
41 | future: true
42 |
43 | # Exclude non-site files
44 | exclude: ['bin', 'README.md']
45 |
46 | # Use the kramdown Markdown renderer
47 | markdown: kramdown
48 | redcarpet:
49 | extensions: [
50 | 'no_intra_emphasis',
51 | 'fenced_code_blocks',
52 | 'autolink',
53 | 'strikethrough',
54 | 'superscript',
55 | 'with_toc_data',
56 | 'tables',
57 | 'hardwrap'
58 | ]
59 |
--------------------------------------------------------------------------------
/docs/_includes/disqus.html:
--------------------------------------------------------------------------------
1 |
10 |
11 | {{ content }}
12 |
--------------------------------------------------------------------------------
/docs/_posts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/_posts/.gitkeep
--------------------------------------------------------------------------------
/docs/_posts/2017-08-07-changelog.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: "Changelog"
4 | category: others
5 | date: 2017-08-07 21:06:03
6 | ---
7 |
8 | ##### 1.1.1
9 |
10 | * Fixes `removeItem`
11 |
12 | #### 1.1.0
13 |
14 | * Gives the possibility to specify a death passage that will replace the default passage when the player is dead
15 | * Fixes various bugs
16 |
17 | ##### 1.0.2
18 |
19 | * Fixes a bug affecting the dragging of items.
20 | * Adds a proper final message for the death screen.
21 |
22 | ##### 1.0.1
23 |
24 | * Changes the version to `1.0.1` since Twine doesn't handle having 2 story formats with the same semver.
25 |
26 | #### 1.0.0
27 |
28 | * Initial release.
--------------------------------------------------------------------------------
/docs/_posts/2017-08-07-choose-the-name.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: "Changing the hero's name"
4 | category: others
5 | date: 2017-08-07 21:06:04
6 | ---
7 |
8 | ### Setting the name
9 |
10 | The name of the character (displayed at the top of the left panel) is stored in the variable `character.name`. It can be changed by writing in one of your passage:
11 |
12 | ```
13 | <%
14 | character.name = "Tanis Half-Elven"
15 | %>
16 | ```
17 |
18 | You can for example put this code in the first passage of your story, if you want the name of the character to be "fixed" (not chosen by the player).
19 |
20 | ### Letting the player choose its name
21 |
22 | This section will show you how to let the player choose its name. Specifically, we will:
23 |
24 | * Make sure the character panel is hidden at the beginning of the game
25 | * Put an text box in the first passage of the game, to let the player choose the name of its character
26 | * Once the player clicks on the link to go to the second passage, display the left panel with the name
27 |
28 | #### Hide the character panel
29 |
30 | In the first passage of the game, add this small script to hide the character panel
31 |
32 | ```
33 | <%
34 | story.config.displayCharacterPanel = false;
35 | %>
36 | ```
37 |
38 | #### Add the text box
39 |
40 | Always in the first passage of the game, add this small HTML code to show a text box where the player can write the name of its character
41 |
42 | ```
43 |
44 | ```
45 |
46 | #### Display the character panel
47 |
48 |
49 | ```
50 | <%
51 | story.config.displayCharacterPanel = true;
52 | %>
53 | ```
54 |
55 | ### Example
56 |
57 | As an example, here is the complete content of the two first passages of a story that would ask the player the name of its character
58 |
59 | #### First passage
60 |
61 | ```
62 | <%
63 | story.config.displayCharacterPanel = false;
64 | %>
65 |
66 | Welcome adventurer, what is your name ?
67 |
68 |
69 |
70 | [[Confirm|Second]]
71 | ```
72 |
73 | #### Second passage
74 |
75 | ```
76 | <%
77 | story.config.displayCharacterPanel = true;
78 | %>
79 |
80 | Your name is <%= character.name %>!
81 |
82 | You wake up in an inn, without any memory of what happened last night...
83 | ```
--------------------------------------------------------------------------------
/docs/_posts/2017-08-07-examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: "Examples"
4 | category: others
5 | date: 2017-08-07 21:06:02
6 | ---
7 |
8 | This page presents some examples (at the moment, only one) of Twine stories using **Adventures**. They serve as "tutorial" and can help you discover how to do cool interactions in your stories.
9 |
10 | To play them, go to the url provided, right-click on the page and `Save Page As...`. Once downloaded, open them in Twine using the `Import from File` button.
11 |
12 | * Complete tutorial: This example shows you how to let the player customizes its name, how to give and remove gold, how to give items and how to customize the death screen.
13 | [Link to the example](https://raw.githubusercontent.com/Longwelwind/adventures/master/examples/complete-tutorial.html)
--------------------------------------------------------------------------------
/docs/_posts/2017-08-07-health-and-gold.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: "Health and Gold"
4 | category: basic-usage
5 | date: 2017-08-07 21:06:03
6 | ---
7 |
8 | ### Health
9 |
10 | You can change the health of the player using the following functions:
11 |
12 | ```javascript
13 | <%
14 | // The character will receive 12 points of damage
15 | character.damage(12);
16 | // The character will be healed of 8 points of damage
17 | character.heal(8);
18 | %>
19 | ```
20 |
21 | The character has a maximum health of 20.
22 |
23 | Its current health is accessible through ```character.health```, for example:
24 |
25 | ```
26 | The innkeeper welcomed you into his place.
27 |
28 | <% if (character.health < 10) { %>
29 |
30 | "It looks like you're hurt, I'll get you a bedroom. No need to pay, it looks like you've got enough trouble today already."
31 |
32 | <% } %>
33 |
34 | ```
35 |
36 | If the health of the character ever falls to 0, the game will trigger a death screen, ending the adventure of the player
37 |
38 | ### Gold
39 |
40 | The amount of gold carried by the player can be changed using the following functions:
41 |
42 | ```javascript
43 | <%
44 | // Add 45 gold to the inventory of the player
45 | character.addGold(45);
46 | // Remove 25 gold to the inventory of the player
47 | character.removeGold(25);
48 | %>
49 | ```
50 |
51 | The amount of gold carried by the player can be accessed through `character.gold`. For example, you can offer the player to buy an item:
52 |
53 | ```
54 | You ask for the price of the sword. The merchant, after peaking at your purse, agrees to sell it to you for 10 gold.
55 |
56 | <% if (character.gold >= 10) { %>
57 | [["Okay !"|Buy the sword]]
58 | <% } %>
59 |
60 | [["I don't want it"|Continue]]
61 | ```
62 |
63 | The link to the passage `Buy the sword` will only appear if the player has at least 10 gold.
64 |
65 | In the passage `Buy the sword`, you can then write:
66 |
67 | ```
68 | <%
69 | character.removeGold(10); // Remove the gold
70 | character.inventory.addItem("sword"); // Give a sword to the player
71 | %>
72 |
73 | "Hope you'll make good use of it !"
74 | ```
--------------------------------------------------------------------------------
/docs/_posts/2017-08-07-installing.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: "Installing"
4 | category: basic-usage
5 | date: 2017-08-07 21:06:05
6 | ---
7 |
8 | To install **Adventures** in Twine 2, click `Formats` in the main menu, then `Add a New Format` and paste the url below into the box. Finally, click `Add`.
9 |
10 | ```
11 | https://cdn.rawgit.com/Longwelwind/adventures/master/dist/format.js
12 | ```
13 |
14 | You can now create new story or edit an existing one and change the story format for your story.
15 |
16 | **Note:** If you're using the web version of Twine 2 (inside your browser), you may run into an error. Try to use the Windows/Linux/OSX version instead!
17 |
18 | ### Interactions
19 |
20 | **Adventures** is based on [klembot's Snowman 2](https://bitbucket.org/klembot/snowman-2). The content of the passages are processed by [lodash's _.template function](https://lodash.com/docs/4.17.4#template), which allows you to execute Javascript scripts inside the `<% %>` tags, and print dynamic values using the `<%= %>` tags.
21 |
22 | For example, the passage:
23 |
24 | ```
25 | <%
26 | var name = "Arthas";
27 | %>
28 |
29 | The old pirate greeted you: "Ahoy, you must be <%= name %>" !
30 | ```
31 |
32 | Would become, once in the story: `The old pirate greeted you: "Ahoy, you must be Arthas" !`.
33 |
34 | You can also conditionaly print content using the classic `if` Javascript structure:
35 |
36 | ```
37 | Along the road, you stumble upon a group of bandits
38 |
39 | <% if (character.money >= 100) { %>
40 |
41 | They notice that your purse seems to be quite heavy
42 |
43 | <% } %>
44 | ```
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/docs/_posts/2017-08-07-items.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: "Items"
4 | category: advanced-usage
5 | date: 2017-08-07 21:06:03
6 | ---
7 |
8 | ### Custom items
9 |
10 | **Adventures** comes with a list of predefined items, but you can define your own custom items to use in your stories.
11 |
12 | Items are defined in the configuration of the story. In Twine 2, in the view of your passages, click on the name of your story in the bottom-left of the screen then click on `Edit Story Javascript`. In it, you can put, for example:
13 |
14 | ```js
15 | window.config = {
16 | "items": [
17 | {
18 | "tag": "sword",
19 | "name": "Steel Sword",
20 | "x": 1, "y": 107
21 | },
22 | {
23 | "tag": "shield",
24 | "name": "Shield",
25 | "x": 2, "y": 71
26 | },
27 | {
28 | "tag": "red-potion",
29 | "name": "Health Potion",
30 | "x": 4, "y": 41
31 | }
32 | ]
33 | }
34 | ```
35 |
36 | The `tag` field is the "programming" name you will use in other function calls, you should only use lowercase alphanumeric characters and use `-` instead of whitespace. The `name` field is the "pretty" name that will be displayed in-game.
37 |
38 | `x` and `y` represents the coordinates of the item in the spritesheet. You can use the [Item chooser]({{ site.baseurl }}{% post_url 2017-08-07-item-chooser %}) to find the coordinate of a specific sprite.
39 |
40 | You can add as many items as you want (don't forget the trailing `,` after each item's closing `}`).
--------------------------------------------------------------------------------
/docs/_posts/2017-08-07-javascript-basics.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: "Javascript basics"
4 | category: basic-usage
5 | date: 2017-08-07 21:06:04
6 | ---
7 |
8 | **Adventures** uses Javascript as a scripting language. Code can be written inside the `<% %>` tags, and values can be printed using the `<%= %>` tags. If you have no knowledge of Javascript, this section will introduce you the basic to make a story.
9 |
10 | ### State
11 |
12 | You can remember values in named variables using the following syntax:
13 |
14 | ```javascript
15 | <%
16 | answer = "accepted";
17 | count = 45;
18 | %>
19 | ```
20 |
21 | In this example, we stored the string of characters `Accepted` in a variable called `answer` and the number `45` in a variable called `count`.
22 |
23 | You can print the content of a variable using the `<%= %>` (Note the `=` sign in the tag). For example:
24 |
25 | ```
26 | When the mage made an offer, you <%= answer %> it.
27 |
28 | You have killed <%= count %> orcs
29 | ```
30 |
31 | Once in the story, this would be printed as:
32 |
33 | ```
34 | When the mage made an offer, you accepted it.
35 |
36 | You have killed 45 orcs
37 | ```
38 |
39 | ## Conditional writing
40 |
41 | You can print text conditionaly, depending on precise conditions. Using the previous variables, we can do, for example:
42 |
43 | ```
44 | <%
45 | if (offer == "accepted") {
46 | %>
47 |
48 | You did accept the offer when you had the choice.
49 |
50 | <%
51 | }
52 | %>
53 | ```
54 |
55 | **Note** the double `=` sign, the opening `{` and the corresponding closing `}`. In this example, the text will only be printed if the variable `offer` contains the value `"accepted"`.
56 |
57 | An other example, here using the `<` operator:
58 |
59 | ```
60 | <%
61 | if (count < 50) {
62 | %>
63 |
64 | You didn't kill enough orcs to satisfy the master.
65 |
66 | <%
67 | }
68 | %>
69 | ```
70 |
71 | ## Functions
72 |
73 | Most of the interactions with **Adventures** is done through functions. A function has a *name*, and you give to it *arguments* when you call it. For example, to give money to the player, you would write:
74 |
75 | ```
76 | You get some gold in the purse of the soldier
77 |
78 | <%
79 | character.addGold(45);
80 | %>
81 | ```
82 |
83 | In this case, you call the function `character.addGold` is the name of the function, and `45` is the argument you give to it.
84 |
85 | The following sections of the documentation describe the different functions you can use to manage your story.
--------------------------------------------------------------------------------
/docs/assets/home-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/home-demo.gif
--------------------------------------------------------------------------------
/docs/assets/items/weapon_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_01.png
--------------------------------------------------------------------------------
/docs/assets/items/weapon_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_02.png
--------------------------------------------------------------------------------
/docs/assets/items/weapon_21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_21.png
--------------------------------------------------------------------------------
/docs/assets/items/weapon_22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_22.png
--------------------------------------------------------------------------------
/docs/assets/items/weapon_221.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_221.png
--------------------------------------------------------------------------------
/docs/assets/items/weapon_222.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_222.png
--------------------------------------------------------------------------------
/docs/assets/items/weapon_61.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_61.png
--------------------------------------------------------------------------------
/docs/assets/items/weapon_62.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Longwelwind/adventures/afc4c0dc552155d546f9dd28ad32cc8661bccb74/docs/assets/items/weapon_62.png
--------------------------------------------------------------------------------
/docs/bin/jekyll-page:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'date'
4 | require 'optparse'
5 |
6 | options = {
7 | # Expects to be in the bin/ sub-directory by default
8 | :path => File.dirname(File.dirname(__FILE__))
9 | }
10 |
11 | parser = OptionParser.new do |opt|
12 | opt.banner = 'usage: jekyll-page TITLE CATEGORY [FILENAME] [OPTIONS]'
13 | opt.separator ''
14 | opt.separator 'Options'
15 | opt.on('-e', '--edit', 'Edit the page') do |edit|
16 | options[:edit] = true
17 | end
18 | opt.on('-l', '--link', 'Relink pages') do |link|
19 | options[:link] = true
20 | end
21 | opt.on('-p PATH', '--path PATH', String, 'Path to project root') do |path|
22 | options[:path] = path
23 | end
24 | opt.separator ''
25 | end
26 |
27 | parser.parse!
28 |
29 | title = ARGV[0]
30 | category = ARGV[1]
31 | filename = ARGV[2]
32 |
33 | # Resolve any relative links
34 | BASE_DIR = File.expand_path(options[:path])
35 | POSTS_DIR = "#{BASE_DIR}/_posts"
36 | PAGES_DIR = "#{BASE_DIR}/_pages"
37 |
38 | # Ensure the _posts directory exists (we are in the correct directory)
39 | if not Dir.exists?(POSTS_DIR)
40 | puts "#{POSTS_DIR} directory does not exists"
41 | exit
42 | end
43 |
44 | # Create _pages directory if it doesn't exist
45 | if not Dir.exists?(PAGES_DIR)
46 | Dir.mkdir(PAGES_DIR)
47 | end
48 |
49 | if options[:link]
50 | Dir.foreach(POSTS_DIR) do |name|
51 | next if name[0] == '.'
52 | nodate = name[/\d{4}-\d{2}-\d{2}-(?.*)/, 'rest']
53 | if File.symlink?("#{PAGES_DIR}/#{nodate}")
54 | File.delete("#{PAGES_DIR}/#{nodate}")
55 | end
56 | abspath = File.absolute_path("#{POSTS_DIR}/#{name}")
57 | File.symlink(abspath, "#{PAGES_DIR}/#{nodate}")
58 | end
59 | end
60 |
61 | if not title or not category
62 | # This flag can be used by itself, exit silently if no arguments
63 | # are defined
64 | if not options[:link]
65 | puts parser
66 | end
67 | exit
68 | end
69 |
70 | if not filename
71 | filename = title.downcase.gsub(/[^a-z0-9\s]/, '').gsub(/\s+/, '-')
72 | end
73 |
74 | today=Date.today().strftime('%F')
75 | now=DateTime.now().strftime('%F %T')
76 |
77 | filepath = "#{POSTS_DIR}/#{today}-#{filename}.md"
78 | symlink = "#{PAGES_DIR}/#{filename}.md"
79 |
80 | if File.exists?(filepath)
81 | puts "File #{filepath} already exists"
82 | exit
83 | end
84 |
85 | content = < .page-header:first-child {
42 | margin-top: 0;
43 | }
44 |
45 | #content > .page-header:first-child h2 {
46 | margin-top: 0;
47 | }
48 |
49 |
50 | #navigation {
51 | font-size: 0.9em;
52 | }
53 |
54 | #navigation li a {
55 | padding-left: 10px;
56 | padding-right: 10px;
57 | }
58 |
59 | #navigation .nav-header {
60 | padding-left: 0;
61 | padding-right: 0;
62 | }
63 |
64 | body.rtl {
65 | direction: rtl;
66 | }
67 |
68 | body.rtl #header .brand {
69 | float: right;
70 | margin-left: 5px;
71 | }
72 | body.rtl .row-fluid [class*="span"] {
73 | float: right !important;
74 | margin-left: 0;
75 | margin-right: 2.564102564102564%;
76 | }
77 | body.rtl .row-fluid [class*="span"]:first-child {
78 | margin-right: 0;
79 | }
80 |
81 | body.rtl ul, body.rtl ol {
82 | margin: 0 25px 10px 0;
83 | }
84 |
85 | table {
86 | margin-bottom: 1rem;
87 | border: 1px solid #e5e5e5;
88 | border-collapse: collapse;
89 | }
90 |
91 | td, th {
92 | padding: .25rem .5rem;
93 | border: 1px solid #e5e5e5;
94 | }
95 |
96 | p > img {
97 | display: block;
98 | margin: 0 auto;
99 | padding: 10px;
100 | border: 1px solid #333;
101 | }
--------------------------------------------------------------------------------
/docs/css/syntax.css:
--------------------------------------------------------------------------------
1 | .highlight .hll { background-color: #ffffcc }
2 | .highlight { background: #ffffff; }
3 | .highlight .c { color: #888888 } /* Comment */
4 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
5 | .highlight .k { color: #008800; font-weight: bold } /* Keyword */
6 | .highlight .cm { color: #888888 } /* Comment.Multiline */
7 | .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
8 | .highlight .c1 { color: #888888 } /* Comment.Single */
9 | .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
10 | .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
11 | .highlight .ge { font-style: italic } /* Generic.Emph */
12 | .highlight .gr { color: #aa0000 } /* Generic.Error */
13 | .highlight .gh { color: #333333 } /* Generic.Heading */
14 | .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
15 | .highlight .go { color: #888888 } /* Generic.Output */
16 | .highlight .gp { color: #555555 } /* Generic.Prompt */
17 | .highlight .gs { font-weight: bold } /* Generic.Strong */
18 | .highlight .gu { color: #666666 } /* Generic.Subheading */
19 | .highlight .gt { color: #aa0000 } /* Generic.Traceback */
20 | .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
21 | .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
22 | .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
23 | .highlight .kp { color: #008800 } /* Keyword.Pseudo */
24 | .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
25 | .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
26 | .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
27 | .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
28 | .highlight .na { color: #336699 } /* Name.Attribute */
29 | .highlight .nb { color: #003388 } /* Name.Builtin */
30 | .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
31 | .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
32 | .highlight .nd { color: #555555 } /* Name.Decorator */
33 | .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
34 | .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
35 | .highlight .nl { color: #336699; font-style: italic } /* Name.Label */
36 | .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
37 | .highlight .py { color: #336699; font-weight: bold } /* Name.Property */
38 | .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
39 | .highlight .nv { color: #336699 } /* Name.Variable */
40 | .highlight .ow { color: #008800 } /* Operator.Word */
41 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */
42 | .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
43 | .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
44 | .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
45 | .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
46 | .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
47 | .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
48 | .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
49 | .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
50 | .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
51 | .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
52 | .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
53 | .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
54 | .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
55 | .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
56 | .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
57 | .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
58 | .highlight .vc { color: #336699 } /* Name.Variable.Class */
59 | .highlight .vg { color: #dd7700 } /* Name.Variable.Global */
60 | .highlight .vi { color: #3333bb } /* Name.Variable.Instance */
61 | .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
62 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: "Home"
4 | ---
5 |
6 | ### What is Adventures
7 |
8 | **Adventures** is a custom story format for [Twine 2](https://twinery.org/) made by [Longwelwind](https://twitter.com/Longwelwind) that allows writers to add RPG elements such as health, items, golds and more to their story. The project is free and open source; feel free to contribute on the [Github repository](https://github.com/Longwelwind/adventures) !
9 |
10 | 
11 |
12 | The documentation is split into 2 sections:
13 |
14 | * **Basic Usage** shows how to use the basic functions of **Adventures** and is perfect for beginners, or those who don't possess sufficient Javascript knowledge as most scripts can be copy/pasted into your story. This section will show you how to change the health of the player, change the gold it holds and add or remove items in its inventory.
15 | * **Advanced Usage** shows the advanced features of **Adventures** and is reserved for those who possess an intermediate knowledge of Javascript and want to make more complex interactions. This section will show you how to create custom items, for example.
16 |
17 | ### Requirements
18 |
19 | **Adventures** is based on [klembot's Snowman 2](https://bitbucket.org/klembot/snowman-2) and works by calling Javascript functions inside `<% %>` tags to change the state of the character and the story. For example, to add a sword to the inventory of the player, you'd write in one of your passages:
20 |
21 | ```
22 | In the middle of the hall, amidst the corpses of the goblins, you find a sword. You're definitly not the first one to wield it, but it should suffice should a monster bar your way.
23 |
24 | <% character.inventory.addItem("sword"); %>
25 | ```
26 |
27 | If you have no knowledge of Javascript, a small section describing the basics of Javascript will help you get the minimum skill to use Adventures.
28 |
--------------------------------------------------------------------------------
/examples/complete-tutorial.html:
--------------------------------------------------------------------------------
1 | <%
12 | // Hide the character panel (on the left)
13 | // It will be brought back in the following passage
14 | story.config.displayCharacterPanel = false;
15 | %>
16 |
17 | Welcome to this small adventure that shows some functionalites of **Adventures**.
18 |
19 | Please enter the name of your hero
20 |
21 | <p><input onchange="character.name = this.value"></p>
22 |
23 | [[Confirm|Second]]<%
24 | // Now that the name of the player is set, we can
25 | // display the character panel.
26 | story.config.displayCharacterPanel = true;
27 | %>
28 |
29 | Welcome <% character.name %>!
30 |
31 | Let's discover a bit more about you. When you were young, you were...
32 |
33 | [[An orphan, forced to steal in order to survive|Orphan]]
34 |
35 | [[The son of a noble, born with a silver spoon in its mouth|Noble]]
36 |
37 | [[Son of a soldier, trained by your father to defend the realm|Soldier]]You're just an orphan.
38 |
39 | [[Continue|Intermediate]]<%
40 | character.addGold(100);
41 | %>
42 |
43 | You're a noble, and as such, you left your house with some gold, to make sure you can buy anythng you need to your journey.
44 |
45 | [[Continue|Intermediate]]<% character.inventory.addItem("sword"); %>
46 |
47 | You're a soldier.
48 |
49 | Since your father trained you when you were a child, you carry a sword with you at all times.
50 |
51 | [[Continue|Intermediate]]You arrive at the main gate of the city. Two guards of the city refuses to let you enter. You...
52 |
53 | <%
54 | // This condition will check wether the player
55 | // has a sword in its inventory. If yes,
56 | // it will display an additional choice.
57 | if (character.inventory.hasItem("sword")) {
58 | %>
59 |
60 | [[[Sword] Attack the guards|Attack]]
61 |
62 | <% } %>
63 |
64 | <%
65 | // This condition will check if the player
66 | // has at least 50 coins in its inventory.
67 | if (character.gold >= 50) {
68 | %>
69 |
70 | [[[50 gold] Bribe the guards|Bribe]]
71 |
72 | <% } %>
73 |
74 | [[Turn back, you didn't even want to go in anyway|Exit]]You slew the guards pretty easily. You proceed into the city.
75 |
76 | As you walk into the city, you feel like you are watched...
77 |
78 | [[Continue|End]]<%
79 | // We remove the gold
80 | character.removeGold(50);
81 | %>
82 |
83 | You give the gold to the guard and proceed into the city.
84 |
85 | You can't help but feel like you're watched...
86 |
87 | [[Continue|End]]
88 |
89 | You turn back.
90 |
91 | You have a bad feeling, as if something terrible was about to happen.
92 |
93 | [[Continue|End]]<%
94 | // We make sure we kill the player
95 | character.damage(character.health);
96 | %>
97 |
98 | You feel a small pinch on you're neck. You realize it was a poisoned dart.<%
99 | // This passage will be displayed whenever the health
100 | // of the player reaches zero. To tell the game to
101 | // show this passage, we specify the name of it ("Death")
102 | // in the setting
103 | %>
104 |
105 | Thanks for playing this small tutorial. This passage has a different theme thanks to the tag added ("theme-red").
106 |
107 | This passage doesn't have any link to other passages, but it is still a passage like others, so you can put whatever you want.
108 |
109 |
--------------------------------------------------------------------------------
/images/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{STORY_NAME}}
4 |
7 |
8 |
9 |
10 | {{STORY_DATA}}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "twine",
3 | "version": "1.1.1",
4 | "description": "",
5 | "main": "webpack.config.js",
6 | "dependencies": {
7 | "@types/jimp": "^0.2.1",
8 | "@types/lodash": "^4.14.70",
9 | "@types/marked": "0.0.28",
10 | "@types/react": "^15.0.38",
11 | "@types/react-dom": "^15.5.1",
12 | "css-loader": "^0.28.4",
13 | "extract-text-webpack-plugin": "^3.0.0",
14 | "jimp": "^0.2.28",
15 | "lodash": "^4.17.4",
16 | "marked": "^0.3.6",
17 | "mobx": "^3.2.1",
18 | "mobx-react": "^4.2.2",
19 | "react": "^15.6.1",
20 | "react-dom": "^15.6.1",
21 | "react-transition-group": "^2.2.0",
22 | "ts-loader": "^2.3.1",
23 | "typescript": "^2.4.2",
24 | "uglify-js": "^3.0.25",
25 | "uglifyjs-webpack-plugin": "^0.4.6",
26 | "webpack": "^3.3.0"
27 | },
28 | "devDependencies": {},
29 | "scripts": {
30 | "start": "webpack --watch",
31 | "build": "webpack -p"
32 | },
33 | "author": "",
34 | "license": "ISC"
35 | }
36 |
--------------------------------------------------------------------------------
/scripts/item-table-generator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This script takes the item defined by the config
3 | * and prints a Markdown table that can be put in the
4 | * documentation
5 | */
6 | import defaultItems from "../src/defaultItems";
7 |
8 | const itemPerRow = 2;
9 | const size = 32;
10 |
11 | console.log("
");
12 |
13 | console.log("
");
14 | for (let i = 0;i < itemPerRow;i++) {
15 | console.log("
tag
name
icon
");
16 | }
17 | console.log("
");
18 |
19 | for (let i = 0;i < Math.ceil(defaultItems.length / itemPerRow);i++) {
20 | console.log("
");
21 | for (let j = 0;j < itemPerRow;j++) {
22 | let index = i*itemPerRow + j;
23 |
24 | if (index < defaultItems.length) {
25 | let item = defaultItems[index];
26 |
27 | console.log(`
${item.tag}
${item.name}
`);
28 | } else {
29 | // This should be an empty cell
30 | console.log(`
`);
31 | }
32 | }
33 |
34 | console.log("
");
35 | }
36 |
37 | console.log("
");
--------------------------------------------------------------------------------
/src/Character.ts:
--------------------------------------------------------------------------------
1 | import { Stat } from './Stat';
2 | import { observable } from 'mobx';
3 | import Inventory from "./Inventory";
4 | import Story from "./Story";
5 |
6 | export default class Character {
7 | @observable name: string = "Aramis";
8 | inventory: Inventory = new Inventory(this.story, 16);
9 | maxHealth: number = 20;
10 | @observable health: number = 20;
11 | @observable gold: number = 0;
12 |
13 | get dead(): boolean {
14 | return this.health == 0;
15 | }
16 |
17 | // Bad design, but it is easier to code than to make a `CharacterStat`
18 | // entity that has a `stat` and a `count` field.
19 | stats: { [key: string]: number } = {};
20 |
21 | constructor(public story: Story) {
22 | this.story.stats.forEach(s => {
23 | this.stats[s.name] = 0;
24 | });
25 | }
26 |
27 | damage(amount: number) {
28 | if (amount < 0) {
29 | throw new Error(`damage: "amount" must be positive`);
30 | }
31 |
32 | this.health = Math.max(this.health - amount, 0);
33 | }
34 |
35 | heal(amount: number) {
36 | if (amount < 0) {
37 | throw new Error(`heal: "amount" must be positive`);
38 | }
39 |
40 | this.health = Math.min(this.health + amount, this.maxHealth);
41 | }
42 |
43 | addGold(gold: number) {
44 | if (gold <= 0) {
45 | return;
46 | }
47 |
48 | this.gold += gold;
49 | }
50 |
51 | removeGold(gold: number) {
52 | if (gold <= 0) {
53 | return;
54 | }
55 |
56 | this.gold -= gold;
57 | }
58 |
59 | hasGold(gold: number) {
60 | return this.gold >= gold;
61 | }
62 |
63 | getStat(tag: string): number {
64 | let stat = this.story.getStat(tag);
65 |
66 | return this.stats[stat.name];
67 | }
68 |
69 | addStat(tag: string, count: number): number {
70 | let stat = this.story.getStat(tag);
71 |
72 | if (count < 0) {
73 | throw new Error("addStat: `count` should be positive");
74 | }
75 |
76 | this.stats[stat.name] += count;
77 | return this.stats[stat.name];
78 | }
79 |
80 | removeStat(tag: string, count: number): number {
81 | let stat = this.story.getStat(tag);
82 |
83 | if (count < 0) {
84 | throw new Error("setStat: `count` should be positive");
85 | }
86 |
87 | this.stats[stat.name] = Math.max(this.stats[stat.name] - count, 0);
88 | return this.stats[stat.name];
89 | }
90 |
91 | setStat(tag: string, count: number): void {
92 | let stat = this.story.getStat(tag);
93 |
94 | if (count < 0) {
95 | throw new Error("setStat: `count` should be positive");
96 | }
97 |
98 | this.stats[stat.name] = count;
99 | }
100 |
101 | hasStat(tag: string, count: number): boolean {
102 | let stat = this.story.getStat(tag);
103 |
104 | return this.stats[stat.name] >= count;
105 | }
106 | }
--------------------------------------------------------------------------------
/src/CharacterComponent.tsx:
--------------------------------------------------------------------------------
1 | import { observer } from 'mobx-react';
2 | import * as React from "react";
3 | import Story from "./Story";
4 | import Character from "./Character";
5 | import ItemComponent from "./ItemComponent";
6 | import Interface from './Interface';
7 |
8 | interface CharacterComponentProps {
9 | story: Story;
10 | }
11 |
12 | @observer
13 | export default class CharacterComponent extends React.Component {
14 |
15 | get story(): Story {
16 | return this.props.story;
17 | }
18 |
19 | get character(): Character {
20 | return this.props.story.character;
21 | }
22 |
23 | render() {
24 | return (
25 |