├── .ruby-version ├── web ├── build │ ├── CNAME │ ├── stylesheets │ │ └── all.css │ ├── javascripts │ │ ├── all.js │ │ └── bootstrap.min.js │ ├── favicon.ico │ ├── images │ │ ├── notepad.png │ │ ├── twee2-logo.png │ │ └── escape-from-earth.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── install.html │ ├── escape-from-earth.tw2 │ ├── index.html │ ├── tutorial.html │ └── documentation.html ├── source │ ├── CNAME │ ├── stylesheets │ │ └── all.css │ ├── javascripts │ │ └── all.js │ ├── favicon.ico │ ├── images │ │ ├── notepad.png │ │ ├── twee2-logo.png │ │ └── escape-from-earth.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── layouts │ │ └── layout.haml │ ├── install.html.haml │ ├── tutorial.html.haml │ ├── escape-from-earth.tw2 │ ├── index.html.haml │ └── documentation.html.haml ├── .gitignore └── config.rb ├── examples └── grunt │ ├── Gemfile │ ├── sections │ ├── section1.tw2 │ └── section2.tw2 │ ├── javascripts │ └── main.js.tw2 │ ├── package.json │ ├── stylesheets │ └── main.sass.tw2 │ ├── story.tw2 │ ├── README.md │ └── Gruntfile.js ├── .gitignore ├── lib ├── twee2 │ ├── version.rb │ ├── build_config.rb │ ├── story_format.rb │ ├── decompiler.rb │ └── story_file.rb └── twee2.rb ├── Gemfile ├── doc └── usage.txt ├── storyFormats ├── Paperthin │ ├── format.js │ └── icon.svg ├── SugarCube │ ├── LICENSE │ └── icon.svg ├── SugarCube2 │ ├── LICENSE │ └── icon.svg ├── Harlowe │ └── icon.svg └── Harlowe2 │ └── icon.svg ├── Rakefile ├── bin └── twee2 ├── twee2.gemspec ├── README.md └── LICENSE /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.1.5 -------------------------------------------------------------------------------- /web/build/CNAME: -------------------------------------------------------------------------------- 1 | twee2.danq.me -------------------------------------------------------------------------------- /web/source/CNAME: -------------------------------------------------------------------------------- 1 | twee2.danq.me -------------------------------------------------------------------------------- /examples/grunt/Gemfile: -------------------------------------------------------------------------------- 1 | gem 'twee2' 2 | -------------------------------------------------------------------------------- /web/build/stylesheets/all.css: -------------------------------------------------------------------------------- 1 | //= require_tree . -------------------------------------------------------------------------------- /web/source/stylesheets/all.css: -------------------------------------------------------------------------------- 1 | //= require_tree . -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | testdata/ 3 | Gemfile.lock 4 | *~ -------------------------------------------------------------------------------- /lib/twee2/version.rb: -------------------------------------------------------------------------------- 1 | module Twee2 2 | VERSION = "0.5.0" 3 | end 4 | -------------------------------------------------------------------------------- /web/build/javascripts/all.js: -------------------------------------------------------------------------------- 1 | //= require jquery-2.1.4.min 2 | //= require_tree . 3 | -------------------------------------------------------------------------------- /web/source/javascripts/all.js: -------------------------------------------------------------------------------- 1 | //= require jquery-2.1.4.min 2 | //= require_tree . 3 | -------------------------------------------------------------------------------- /web/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/favicon.ico -------------------------------------------------------------------------------- /web/source/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/favicon.ico -------------------------------------------------------------------------------- /examples/grunt/sections/section1.tw2: -------------------------------------------------------------------------------- 1 | ::Game 2 | 3 | You can go to [[Room 1]] or [[Room 2]] 4 | -------------------------------------------------------------------------------- /web/build/images/notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/images/notepad.png -------------------------------------------------------------------------------- /examples/grunt/javascripts/main.js.tw2: -------------------------------------------------------------------------------- 1 | ::Boring Javascript [script] 2 | 3 | console.log('it works!') 4 | -------------------------------------------------------------------------------- /web/source/images/notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/images/notepad.png -------------------------------------------------------------------------------- /web/build/images/twee2-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/images/twee2-logo.png -------------------------------------------------------------------------------- /web/source/images/twee2-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/images/twee2-logo.png -------------------------------------------------------------------------------- /web/build/images/escape-from-earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/images/escape-from-earth.png -------------------------------------------------------------------------------- /web/source/images/escape-from-earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/images/escape-from-earth.png -------------------------------------------------------------------------------- /web/build/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /web/build/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /web/build/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /web/build/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/build/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /web/source/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /web/source/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /web/source/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /web/source/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dan-Q/twee2/HEAD/web/source/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /examples/grunt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "grunt": "^1.0.1", 4 | "grunt-contrib-watch": "^1.0.0", 5 | "grunt-run": "^0.8.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/grunt/sections/section2.tw2: -------------------------------------------------------------------------------- 1 | ::Room 1 2 | You Win! 3 | 4 | [[Start Over->Start]] 5 | 6 | ::Room 2 7 | Sorry, You Lose! 8 | 9 | [[Start Over->Start]] 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'thor' 4 | gem 'builder' 5 | gem 'filewatcher' 6 | gem 'haml' 7 | gem 'coffee-script' 8 | gem 'nokogiri' 9 | gem 'sass' -------------------------------------------------------------------------------- /examples/grunt/stylesheets/main.sass.tw2: -------------------------------------------------------------------------------- 1 | ::Main Stylesheet [sass stylesheet] 2 | 3 | body 4 | background-color: blue 5 | 6 | tw-story 7 | font-size: 2rem 8 | color: white 9 | 10 | tw-link 11 | color: red 12 | -------------------------------------------------------------------------------- /examples/grunt/story.tw2: -------------------------------------------------------------------------------- 1 | ::StoryTitle 2 | War Games 3 | 4 | ::StoryIncludes 5 | stylesheets/main.sass.tw2 6 | javascripts/main.js.tw2 7 | sections/section1.tw2 8 | sections/section2.tw2 9 | 10 | ::Start 11 | Shall We Play A [[Game]]? 12 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | /.bundle 9 | 10 | # Ignore cache 11 | /.sass-cache 12 | /.cache 13 | 14 | # Ignore .DS_store file 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /examples/grunt/README.md: -------------------------------------------------------------------------------- 1 | # Grunt Example 2 | 3 | You way wish to use grunt to give you a wider variety of build/compile/watch options. The following shows a basic grunt setup where all folders are watched for changes to files, twee2 builds the main `story.html` file automatically on every change, and it is live-reloaded in your browser. 4 | 5 | ## SETUP 6 | 7 | `bundle install` 8 | 9 | `npm install` 10 | 11 | `grunt watch` 12 | 13 | ## References 14 | * [Grunt](https://gruntjs.com/getting-started) 15 | 16 | * [LiveReload extensions](https://github.com/livereload/livereload-extensions) 17 | -------------------------------------------------------------------------------- /examples/grunt/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | run: { 5 | twee2: { 6 | exec: 'bundle exec twee2 build story.tw2 story.html' 7 | } 8 | }, 9 | watch: { 10 | files: ['*.tw2', 'stylesheets/*.tw2', 'javascripts/*.tw2', 'sections/*.tw2'], 11 | tasks: ['run:twee2'], 12 | livereload: { 13 | options: { livereload: true }, 14 | files: ['story.html'], 15 | }, 16 | }, 17 | }); 18 | 19 | grunt.loadNpmTasks('grunt-contrib-watch'); 20 | grunt.loadNpmTasks('grunt-run'); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /lib/twee2/build_config.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | require 'securerandom' 3 | 4 | module Twee2 5 | class BuildConfig 6 | include Singleton 7 | 8 | attr_accessor :story_format, :story_file, :story_name 9 | attr_reader :story_ifid, :story_ifid_specified 10 | 11 | # Set defaults 12 | def initialize 13 | @story_name = 'An unnamed story' 14 | @story_ifid, @story_ifid_specified = SecureRandom.uuid, false 15 | end 16 | 17 | # Set the IFID - we track when this occurs so that the user can be 18 | # nagged for not manually setting it 19 | def story_ifid=(value) 20 | @story_ifid = value 21 | @story_ifid_specified = true 22 | end 23 | end 24 | 25 | def self.build_config 26 | BuildConfig::instance 27 | end 28 | end -------------------------------------------------------------------------------- /doc/usage.txt: -------------------------------------------------------------------------------- 1 | Command-line compiler turning Twee-like files into Twine2-like output. 2 | ------------------------------------------------------------------------------- 3 | Usage: 4 | 5 | twee2 build [input.tw2] [output.html] [[--format=story_format]] 6 | Compiles the specified twee-like file into an output file. optionally 7 | specify the format (Harlowe, Paperthin, Snowman, SugarCube, etc.). 8 | 9 | twee2 watch [input.tw2] [output.html] [[--format=story_format]] 10 | Syntax is the same as build, but twee2 will automatically watch for 11 | changes to your input file and recompile dynamically. 12 | 13 | twee2 formats 14 | Lists the output formats that are understood. 15 | 16 | twee2 decompile [URL] [output.tw2] 17 | Decompiles a Twee2/Twine 2 HTML output file at a specified URL into 18 | a Twee2 source file. 19 | NOT AVAILABLE ON MICROSOFT WINDOWS. 20 | 21 | twee2 version 22 | Reports what version of Twee2 you're using, and checks what the 23 | latest-available version is. 24 | 25 | twee2 help 26 | Displays this message. 27 | -------------------------------------------------------------------------------- /storyFormats/Paperthin/format.js: -------------------------------------------------------------------------------- 1 | window.storyFormat({"name":"Paperthin","version":"1.0","description":"The default proofing format for Twine 2. Icon designed by Simon Child from the Noun Project","author":"Chris Klimas","image":"icon.svg","url":"http://twinery.org/","license":"ZLib/Libpng","proofing":true,"source":"\n\n\n{{STORY_NAME}}\n\n\n\n\n\n\n\n

{{STORY_NAME}}\n

\n{{STORY_DATA}}\n\n\n\n\n"}); -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | namespace :web do 4 | # Yields to a block after chdir'ing to the specified 5 | # path (relative to the app root), then chdir's back 6 | def run_from_directory(path) 7 | old_dir = Dir::pwd 8 | Dir::chdir("#{File::dirname(__FILE__)}/#{path}") 9 | yield 10 | Dir::chdir(old_dir) 11 | end 12 | 13 | desc 'Build the website from source' 14 | task :build do 15 | raise 'Middleman not found. Try "gem install middleman".' if `which middleman` == '' 16 | run_from_directory('web') do 17 | system("middleman build --clean") 18 | end 19 | end 20 | 21 | desc "Preview the website at http://0.0.0.0:4567" 22 | task :preview do 23 | raise 'Middleman not found. Try "gem install middleman".' if `which middleman` == '' 24 | run_from_directory('web') do 25 | system("middleman server --bind-address=0.0.0.0") 26 | end 27 | end 28 | 29 | desc "Deploy the website to github pages" 30 | task :deploy do 31 | run_from_directory('.') do 32 | system("git subtree push --prefix web/build origin gh-pages") 33 | end 34 | end 35 | 36 | desc "Build and deploy the website" 37 | task :build_dep => [:build, :deploy] do 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /bin/twee2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'twee2' 4 | 5 | class Twee2CLI < Thor 6 | package_name 'Twee2' 7 | map '-h' => :help 8 | map '--help' => :help 9 | 10 | desc 'build [INPUT] [OUTPUT]', 'compiles a Twee-like file into Twee2-like output' 11 | method_option :format, type: :string, default: Twee2::DEFAULT_FORMAT 12 | def build(input, output) 13 | Twee2::build(input, output, options) 14 | end 15 | 16 | desc 'watch', 'like "build", but watches for changes and automatically rebuilds on the fly' 17 | method_option :format, type: :string, default: Twee2::DEFAULT_FORMAT 18 | def watch(input, output) 19 | Twee2::watch(input, output, options) 20 | end 21 | 22 | desc 'formats', 'lists understood output formats' 23 | def formats 24 | Twee2::formats 25 | end 26 | 27 | desc 'decompile [URL] [OUTPUT]', 'reverse-engineers Twee2/Twine 2 HTML output into a Twee2 source file' 28 | def decompile(url, output) 29 | Twee2::decompile(url, output) 30 | end 31 | 32 | desc 'version', "reports which version you're using and checks what the highest available version is" 33 | def version 34 | Twee2::version_check 35 | end 36 | 37 | desc 'help', 'shows usage instructions' 38 | def help 39 | Twee2::help 40 | end 41 | 42 | default_task :help 43 | end 44 | 45 | Twee2CLI.start 46 | -------------------------------------------------------------------------------- /storyFormats/SugarCube/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2013-2015 Thomas Michael Edwards . 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /storyFormats/SugarCube2/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2013-2016 Thomas Michael Edwards . 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /lib/twee2/story_format.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module Twee2 4 | class StoryFormatNotFoundException < Exception; end 5 | 6 | class StoryFormat 7 | # Loads the StoryFormat with the specified name 8 | def initialize(name) 9 | raise(StoryFormatNotFoundException) if !File::exists?(format_file_path = Twee2::buildpath("storyFormats/#{name}/format.js")) && !File::exists?(format_file_path = "#{name}/format.js") 10 | @name = name 11 | format_file = File::read(format_file_path) 12 | format_data = format_file.match(/(["'])source\1 *: *(["']).*?[^\\]\2/)[0] 13 | format_data_for_json = "\{#{format_data}\}" 14 | @source = JSON.parse(format_data_for_json)['source'] 15 | end 16 | 17 | # Given a story file, injects it into the StoryFormat and returns the HTML results 18 | def compile 19 | @source.gsub('{{STORY_NAME}}', Twee2::build_config.story_name).gsub('{{STORY_DATA}}', Twee2::build_config.story_file.xmldata).gsub('{{STORY_FORMAT}}', @name) 20 | end 21 | 22 | # Returns an array containing the known StoryFormat names 23 | def self.known_names 24 | Dir.open(Twee2::buildpath('storyFormats')).to_a.sort.reject{|d|d=~/^\./}.map do |name| 25 | format_file_path = Twee2::buildpath("storyFormats/#{name}/format.js") 26 | format_file = File::read(format_file_path) 27 | version = format_file.match(/(["'])version\1 *: *(["'])(.*?[^\\])\2/)[3] 28 | " * #{name} (#{version})" 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /web/config.rb: -------------------------------------------------------------------------------- 1 | ### 2 | # Compass 3 | ### 4 | 5 | # Change Compass configuration 6 | # compass_config do |config| 7 | # config.output_style = :compact 8 | # end 9 | 10 | ### 11 | # Page options, layouts, aliases and proxies 12 | ### 13 | 14 | # Per-page layout changes: 15 | # 16 | # With no layout 17 | # page "/path/to/file.html", :layout => false 18 | # 19 | # With alternative layout 20 | # page "/path/to/file.html", :layout => :otherlayout 21 | # 22 | # A path which all have the same layout 23 | # with_layout :admin do 24 | # page "/admin/*" 25 | # end 26 | 27 | # Proxy pages (https://middlemanapp.com/advanced/dynamic_pages/) 28 | # proxy "/this-page-has-no-template.html", "/template-file.html", :locals => { 29 | # :which_fake_page => "Rendering a fake page with a local variable" } 30 | 31 | ### 32 | # Helpers 33 | ### 34 | 35 | # Automatic image dimensions on image_tag helper 36 | # activate :automatic_image_sizes 37 | 38 | # Reload the browser automatically whenever files change 39 | # configure :development do 40 | # activate :livereload 41 | # end 42 | 43 | # Methods defined in the helpers block are available in templates 44 | # helpers do 45 | # def some_helper 46 | # "Helping" 47 | # end 48 | # end 49 | 50 | set :css_dir, 'stylesheets' 51 | 52 | set :js_dir, 'javascripts' 53 | 54 | set :images_dir, 'images' 55 | 56 | # Build-specific configuration 57 | configure :build do 58 | # For example, change the Compass output style for deployment 59 | # activate :minify_css 60 | 61 | # Minify Javascript on build 62 | # activate :minify_javascript 63 | 64 | # Enable cache buster 65 | # activate :asset_hash 66 | 67 | # Use relative URLs 68 | # activate :relative_assets 69 | 70 | # Or use a different image path 71 | # set :http_prefix, "/Content/images/" 72 | end 73 | -------------------------------------------------------------------------------- /web/source/layouts/layout.haml: -------------------------------------------------------------------------------- 1 | !!! html 2 | %html 3 | %head 4 | %meta{charset: 'utf-8'} 5 | %title 6 | Twee2 7 | = " | #{current_page.data.title}" if current_page.data.title 8 | %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} 9 | %meta{name: 'viewport', content: 'width=device-width, initial-scale=1'} 10 | %link{href: 'stylesheets/all.css', rel: 'stylesheet'}/ 11 | %script{src: 'javascripts/all.js'} 12 | %body 13 | .navbar.navbar-default.navbar-static-top 14 | .container 15 | .navbar-header 16 | %a.navbar-brand{href: './'} Twee2 17 | %button.navbar-toggle{type: 'button', data: { toggle: 'collapse', target: '#navbar-main'} } 18 | - 3.times do 19 | %span.icon-bar 20 | #navbar-main.navbar-collapse.collapse 21 | %ul.nav.navbar-nav 22 | %li 23 | %a{href: 'index.html'} Home 24 | %li 25 | %a{href: 'install.html'} Install 26 | %li 27 | %a{href: 'tutorial.html'} 2-minute tutorial 28 | %li 29 | %a{href: 'documentation.html'} Documentation 30 | %li 31 | %a{href: 'https://github.com/Dan-Q/twee2'} Source 32 | 33 | .container 34 | = yield 35 | 36 | %footer 37 | %hr 38 | .row 39 | .col-lg-12 40 | %ul.list-unstyled 41 | %li.pull-right 42 | %a{href: '#top'} Back to top 43 | %p.small 44 | © 45 | %a{href: 'https://danq.me/'} Dan Q 46 | 2015— 47 | | 48 | Twee2 is distributed 49 | %a{href: 'https://github.com/Dan-Q/twee2'} via Github 50 | under the 51 | %a{href: 'https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html'} GNU General Public License version 2 52 | | 53 | This website is licensed under the 54 | %a{href: 'http://opencontent.org/openpub/'} Open Publication License version 1 -------------------------------------------------------------------------------- /twee2.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'twee2/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "twee2" 8 | spec.version = Twee2::VERSION 9 | spec.authors = ["Dan Q"] 10 | spec.email = ["dan@danq.me"] 11 | spec.summary = %q{Command-line tool to compile Twee-style (.tw, .twine) interactive fiction source files to Twine 2-style (non-Tiddlywiki) output.} 12 | spec.description = <<-EOF 13 | Designed for those who preferred the Twee (for Twine 1) approach to source management, because the command-line is awesome, 14 | but who want to take advantage of the new features in Twine 2. 15 | Note that this is NOT a Twine 1 to Twine 2 converter, although parts of its functionality go some way to achieving this goal. 16 | EOF 17 | spec.homepage = "https://github.com/Dan-Q/twee2" 18 | spec.license = "GPL-2.0" 19 | 20 | spec.files = `git ls-files -z`.split("\x0") 21 | spec.bindir = 'bin' 22 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 23 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 24 | spec.require_paths = ["lib"] 25 | 26 | spec.required_ruby_version = '~> 2' 27 | 28 | spec.add_development_dependency 'rake', '~> 10' 29 | spec.add_development_dependency 'middleman', '>= 3.4.0' 30 | 31 | spec.add_runtime_dependency 'builder', '~> 3.2', '>= 3.2.2' 32 | spec.add_runtime_dependency 'bundler', '~> 1.6' 33 | spec.add_runtime_dependency 'coffee-script', '~> 2.4', '>= 2.4.1' 34 | spec.add_runtime_dependency 'coffee-script-source', '~> 1.9', '>= 1.9.1.1' 35 | spec.add_runtime_dependency 'execjs', '~> 2.6', '>= 2.6.0' 36 | spec.add_runtime_dependency 'filewatcher', '~> 0.5', '>= 0.5.2' 37 | spec.add_runtime_dependency 'haml', '~> 4.0', '>= 4.0.7' 38 | spec.add_runtime_dependency 'nokogiri', '~> 1.6', '>= 1.6.6.2' 39 | spec.add_runtime_dependency 'sass', '~> 3.2', '>= 3.2.19' 40 | spec.add_runtime_dependency 'thor', '~> 0.19', '>= 0.19.1' 41 | spec.add_runtime_dependency 'tilt', '~> 2.0', '>= 2.0.1' 42 | spec.add_runtime_dependency 'trollop', '~> 2.1', '>= 2.1.2' 43 | end 44 | -------------------------------------------------------------------------------- /web/source/install.html.haml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Install 3 | --- 4 | 5 | .row 6 | .col-sm-12 7 | %h1 Installing Twee2 8 | %h2 Prerequisites 9 | %p 10 | Twee2 requires 11 | %a{href: 'https://ruby-lang.org/'} Ruby 12 | version 2.0 or higher. You can check whether Ruby is installed and what version it is 13 | by opening up a command prompt or terminal and running: 14 | %pre 15 | ruby -v 16 | %p 17 | If you don't have Ruby installed, there are several ways to easily install it. Here are some quick ways: 18 | 19 | .row 20 | .col-sm-4 21 | .well 22 | %h3 Windows 23 | %p 24 | %a{href: 'http://rubyinstaller.org/downloads/'} Download RubyInstaller 25 | %p.small 26 | Note that the 'decompile' feature, which turns Twine 2 story files into Twee2 source code, is not supported on Windows. 27 | 28 | .col-sm-4 29 | .well 30 | %h3 MacOS 31 | %p 32 | MacOS comes with Ruby, but it's usually out-of-date. 33 | %a{href: 'https://www.interworks.com/blog/ckaukis/2013/03/05/installing-ruby-200-rvm-and-homebrew-mac-os-x-108-mountain-lion'} 34 | Install a modern version 35 | using RVM. 36 | 37 | .col-sm-4 38 | .well 39 | %h3 Linux 40 | %p 41 | Many Linux distributions have packages for Ruby, but it's often out-of-date. 42 | Install 43 | %acronym{title: 'Ruby Version Manager'} RVM 44 | and use it to get Ruby 2.1.6 by running: 45 | %code.small 46 | :preserve 47 | gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 48 | \curl -sSL https://get.rvm.io | bash -s stable --ruby=2.1.6 49 | rvm use 2.1.6 --default 50 | 51 | .row 52 | .col-sm-12 53 | %h2 Installing Twee2 54 | %p 55 | To install Twee2, open a command prompt or terminal and type: 56 | %pre 57 | gem install twee2 58 | %p 59 | You can check that it's installed correctly by running: 60 | %pre 61 | twee2 62 | %p 63 | Now you're ready to dive into the 64 | %a{href: 'tutorial.html'} two-minute tutorial 65 | or the 66 | = succeed '.' do 67 | %a{href: 'documentation.html'} full documentation 68 | 69 | .row 70 | .col-sm-6 71 | %h2 Uninstalling Twee2 72 | %p 73 | If you ever want to uninstall Twee2, simply run: 74 | %pre 75 | gem uninstall twee2 76 | 77 | .col-sm-6 78 | %h2 Source code 79 | %p 80 | Twee2 is open-source, which means you can read and change its code for yourself. 81 | %a{href: 'https://github.com/Dan-Q/twee2'} Get the code on GitHub. 82 | -------------------------------------------------------------------------------- /lib/twee2/decompiler.rb: -------------------------------------------------------------------------------- 1 | unless Gem.win_platform? 2 | require 'rubygems' 3 | require 'open-uri' 4 | require 'nokogiri' 5 | 6 | module Twee2 7 | class DecompilationFailedException < Exception; end 8 | 9 | class Decompiler 10 | def self.decompile(url) 11 | result = '' 12 | # Load the compiled HTML and sanity-check it 13 | html = Nokogiri::HTML(open(url)) 14 | raise(DecompilationFailedException, 'tw-storydata not found') unless storydata = html.at_css('tw-storydata') 15 | # Extract the tw-storydata#name (StoryTitle) and #startnode 16 | result << "::StoryTitle\n#{storydata[:name].strip}\n\n" 17 | startnode_pid, startnode_name = storydata[:startnode].strip, nil 18 | # Extract the custom CSS and Javascript, if applicable 19 | if (css = storydata.at_css('#twine-user-stylesheet')) && ((css_content = css.content.strip) != '') 20 | result << "::StoryCSS [stylesheet]\n#{css_content}\n\n" 21 | end 22 | if (js = storydata.at_css('#twine-user-script')) && ((js_content = js.content.strip) != '') 23 | result << "::StoryJS [script]\n#{js.content}\n\n" 24 | end 25 | # Extract each passage 26 | storydata.css('tw-passagedata').each do |passagedata| 27 | # Check if this is the start passage and record this accordingly 28 | startnode_name = passagedata[:name] if(startnode_pid == passagedata[:pid]) 29 | # Write the passage out 30 | result << "::#{passagedata[:name].strip}" 31 | result << " [#{passagedata[:tags].strip}]" if passagedata[:tags].strip != '' 32 | result << " <#{passagedata[:position].strip}>" if passagedata[:position].strip != '' 33 | result << "\n#{tidyup_passagedata(passagedata.content.strip)}\n\n" 34 | end 35 | # Write the Twee2 settings out (compatability layer) 36 | result << "::Twee2Settings [twee2]\n" 37 | result << "@story_start_name = '#{startnode_name.gsub("'", "\\'")}'\n" if startnode_name 38 | result << "\n" 39 | # Return the result 40 | result 41 | end 42 | 43 | protected 44 | 45 | # Fixes common problems with decompiled passage content 46 | def self.tidyup_passagedata(passagedata_content) 47 | passagedata_content.gsub(/\[\[ *(.*?) *\]\]/, '[[\1]]'). # remove excess spacing within links: not suitable for Twee-style source 48 | gsub(/\[\[ *(.*?) *<- *(.*?) *\]\]/, '[[\1<-\2]]'). # ditto 49 | gsub(/\[\[ *(.*?) *-> *(.*?) *\]\]/, '[[\1->\2]]'). # ditto 50 | gsub(/\[\[ *(.*?) *\| *(.*?) *\]\]/, '[[\1|\2]]') # ditto 51 | end 52 | end 53 | end 54 | end -------------------------------------------------------------------------------- /storyFormats/Harlowe/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | H 72 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /storyFormats/Harlowe2/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | H 72 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /lib/twee2.rb: -------------------------------------------------------------------------------- 1 | # Prerequisites (managed by bundler) 2 | require 'rubygems' 3 | require 'bundler/setup' 4 | Dir.glob("#{File.dirname(File.absolute_path(__FILE__))}/twee2/*.rb", &method(:require)) 5 | require 'thor' 6 | require 'filewatcher' 7 | 8 | module Twee2 9 | # Constants 10 | DEFAULT_FORMAT = 'Harlowe' 11 | 12 | def self.build(input, output, options = {}) 13 | # Read and parse input file 14 | begin 15 | build_config.story_file = StoryFile::new(input) 16 | rescue StoryFileNotFoundException 17 | puts "ERROR: story file '#{input}' not found." 18 | exit 19 | end 20 | # Read and parse format file, unless already set (by a Twee2::build_config.story_format call in the story file, for example) 21 | if !build_config.story_format 22 | begin 23 | build_config.story_format = StoryFormat::new(options[:format]) 24 | rescue StoryFormatNotFoundException 25 | puts "ERROR: story format '#{options[:format]}' not found." 26 | exit 27 | end 28 | end 29 | # Load story format, if for some reason Twee2::build_config.story_format is set to a string rather than an instance 30 | if build_config.story_format.is_a?(String) 31 | new_format = build_config.story_format 32 | begin 33 | build_config.story_format = StoryFormat::new(new_format) 34 | rescue StoryFormatNotFoundException 35 | puts "ERROR: story format '#{new_format}' not found." 36 | exit 37 | end 38 | end 39 | # Warn if IFID not specified 40 | if !build_config.story_ifid_specified 41 | puts "NOTICE: You haven't specified your IFID. Consider adding to your code -" 42 | puts "::StoryIFID[twee2]\nTwee2::build_config.story_ifid = '#{build_config.story_ifid}'" 43 | end 44 | # Produce output file 45 | File::open(output, 'w') do |out| 46 | out.print build_config.story_format.compile 47 | end 48 | puts "Done" 49 | end 50 | 51 | def self.watch(input, output, options = {}) 52 | puts "Compiling #{output}" 53 | build(input, output, options) 54 | puts "Watching #{input} and included children" 55 | watch_files = build_config.story_file.child_story_files 56 | watch_files.unshift(input) 57 | FileWatcher.new(watch_files).watch do |filename| 58 | puts "#{filename} changed. Recompiling #{output}" 59 | build(input, output, options) 60 | end 61 | end 62 | 63 | def self.formats 64 | puts "I understand the following output formats:" 65 | puts StoryFormat.known_names.join("\n") 66 | end 67 | 68 | def self.version_check 69 | `gem list -r -q twee2` =~ /\((.*)\)/ 70 | puts " Your version: #{Twee2::VERSION}" 71 | puts " Latest version: #{$1}" 72 | if Twee2::VERSION.to_s != $1 73 | puts "To upgrade, run: gem install twee2" 74 | end 75 | end 76 | 77 | unless Gem.win_platform? 78 | # Reverse-engineers a Twee2/Twine 2 output HTML file into a Twee2 source file 79 | def self.decompile(url, output) 80 | File::open(output, 'w') do |out| 81 | out.print Decompiler::decompile(url) 82 | end 83 | puts "Done" 84 | end 85 | end 86 | 87 | def self.help 88 | puts "Twee2 #{Twee2::VERSION}" 89 | puts File.read(buildpath('doc/usage.txt')) 90 | end 91 | 92 | def self.buildpath(path) 93 | File.join(File.dirname(File.expand_path(__FILE__)), "../#{path}") 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /storyFormats/SugarCube/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | image/svg+xml 20 | 21 | 22 | 23 | 24 | Open Clip Art Library 25 | 26 | 27 | Sugar Cube icon 28 | 2010-10-10T11:46:52 29 | A sugar cube. 30 | http://openclipart.org/detail/89407/sugar-cube-icon-by-jhnri4 31 | 32 | 33 | jhnri4 34 | 35 | 36 | 37 | 38 | SVG 39 | block 40 | clip art 41 | clipart 42 | cube 43 | icon 44 | sugar 45 | white 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /storyFormats/SugarCube2/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | image/svg+xml 20 | 21 | 22 | 23 | 24 | Open Clip Art Library 25 | 26 | 27 | Sugar Cube icon 28 | 2010-10-10T11:46:52 29 | A sugar cube. 30 | http://openclipart.org/detail/89407/sugar-cube-icon-by-jhnri4 31 | 32 | 33 | jhnri4 34 | 35 | 36 | 37 | 38 | SVG 39 | block 40 | clip art 41 | clipart 42 | cube 43 | icon 44 | sugar 45 | white 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twee2 2 | 3 | Command-line tool to compile Twee-style (.tw, .twine) interactive fiction source files to [Twine 2](http://twinery.org/)-style output. Use your favourite text editor to write Twine 2 interactive fiction. 4 | 5 | Designed for those who preferred the Twee approach to source management, because the command-line is awesome, but who want to take advantage of the new features in Twine 2. With a little work, this tool may also function as a partial Twine 1 to Twine 2 converter. 6 | 7 | For installation and usage, see https://dan-q.github.io/twee2/ 8 | 9 | ## Philosophy 10 | 11 | (Why does this exist? Where is it going?) 12 | 13 | I love the direction that Twine 2 has been going in, in regard to ditching the old Tiddlywiki backend and making it easier than ever for developers to integrate their own CSS and Javascript into their stories. However, as a fan of plain-old text editors and not of IDEs, I'm not so keen on the fact that it's now almost-impossible to develop a Twine adventure from the command-line only (there's no "Twee" equivalent for Twine 2). For my own benefit and enjoyment, I aim to fill that gap. If it helps you too, then that's just a bonus. 14 | 15 | I'd love to hear your thoughts about the future of this gem. Pull requests are also welcome. 16 | 17 | ## Installation 18 | 19 | Install using gem 20 | 21 | gem install twee2 22 | 23 | * [Full installation instructions](https://dan-q.github.io/twee2/install.html). 24 | 25 | For errors involving nokogiri, see [Installing Nokogiri](http://www.nokogiri.org/tutorials/installing_nokogiri.html). 26 | 27 | ## Basic Usage 28 | 29 | To compile a Twee file into a HTML file using the default format (Harlowe): 30 | 31 | twee2 build inputfile.twee outputfile.html 32 | 33 | To use a specific format, e.g. Snowman: 34 | 35 | twee2 build inputfile.twee outputfile.html --format=Snowman 36 | 37 | For additional features (e.g. listing known formats, watch-for-changes mode), run twee2 without any parameters. Or see the full documentation at https://dan-q.github.io/twee2/documentation.html. 38 | 39 | ## Special features 40 | 41 | Aside from the obvious benefits of a "use your own editor" solution, Twee2 provides the following enhancements over Twine 2: 42 | 43 | * Multi-file inclusion - spread your work over multiple files to improve organisation, facilitate better source control, collaborate with others, or make re-usable modules. 44 | * [HAML](http://haml.info/) support - for those who prefer HAML to Markdown, and for advanced embedding of scripting into passages. 45 | * [Coffeescript](http://coffeescript.org/) transpiler - optionally write your Javascript in Coffeescript for a smarter, lighter syntax. 46 | * [SASS/SCSS](http://sass-lang.com/) stylesheets - optionally enhance your CSS with syntactic awesome. 47 | * Ruby-powered dynamic generation - automate parts of your build chain (e.g. how about a procedurally-built maze of twisty little passages... or even something actually *good*) with Ruby scripting 48 | * Twine 2 decompilation - reverse-engineer Twine 2 stories into Twee2 source code to convert your projects or to understand other people's. 49 | 50 | ## Notes 51 | 52 | * This is not a Twee to Harlowe converter. You'll still need to change your macros as described at http://twine2.neocities.org/, and/or rewrite them as Javascript code. However, it might help your efforts to update Twee sources to Twine 2 output. 53 | * Some special story segments (e.g. StorySubtitle) used in Twee 1 are ignored. You will be warned when this happens. 54 | * The Twine 2 editor might not be able to re-open stories compiled using Twee2, because Twee2 does not automatically include positional data used by the visual editor (however, you can add this manually if you like). 55 | 56 | ## Build 57 | 58 | To build a local copy of the gem: 59 | 60 | gem build twee2.gemspec 61 | 62 | To install it locally (e.g., for version number 0.5.0): 63 | 64 | gem install twee2-0.5.0.gem 65 | 66 | ## License 67 | 68 | This code is released under the GPL, version 2. It includes code (in the storyFormats directory) by other authors, including Leon Arnott: please read their licenses before redistributing. 69 | -------------------------------------------------------------------------------- /web/source/tutorial.html.haml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Start writing Interactive Fiction in two minutes 3 | --- 4 | 5 | .row 6 | .col-sm-12 7 | %h1 2-minute tutorial 8 | %p 9 | You can write your first piece of interactive fiction in just two minutes, after you've installed 10 | Twee2. Just follow the steps below. When you're ready to move on to more-complicated adventures, 11 | see the 12 | = succeed '.' do 13 | %a{href: 'documentation.html'} full documentation 14 | 15 | .row 16 | .col-xs-8 17 | %h2 1. Install Twee2 18 | %p 19 | If you haven't 20 | %a{href: 'install.html'} installed Twee2 21 | yet, do that first. 22 | .col-xs-4 23 | %a.btn.btn-primary.btn-lg{role: 'button', href: 'install.html'} 24 | %span.glyphicon.glyphicon-cloud-download{aria: { hidden: true }} 25 | Install Twee2 26 | 27 | .row 28 | .col-sm-6 29 | %h2 2. Open your text editor 30 | %p 31 | You can write code in any text editor: your computer will almost-certainly already have one installed. 32 | On Windows, you can use Notepad. On MacOS, TextEdit is available (be sure to select "Make Plain Text" 33 | from the Format menu). On Linux, you might use GEdit, Kate, Emacs, Vim, Pico, or something else entirely. 34 | %p 35 | If you have a favourite text editor, you can use that: personally, I like 36 | = succeed ',' do 37 | %a{href: 'https://www.sublimetext.com'} Sublime Text 38 | which is available for a variety of platforms and supports 39 | = succeed '.' do 40 | %a{href: 'https://github.com/monospaced/sublime-twee'} source code highlighting for Twee2 41 | .col-sm-6{style: 'margin-bottom: 2em'} 42 | %img.center-block.img-responsive{src: 'images/notepad.png', alt: 'Notepad for Windows'} 43 | 44 | .row 45 | .col-sm-6 46 | %h2 3. Write some code 47 | %p 48 | To begin with, copy-paste the code 49 | %span.visible-xs-inline below 50 | %span.hidden-xs on the right 51 | into your text editor (or 52 | %a{href: 'escape-from-earth.tw2'} download it here 53 | \- you might need to right-click and select "Save as..."). 54 | Later, you can experiment with changing this file. 55 | %p 56 | Notice that the file is broken up into several 57 | = succeed ',' do 58 | %em passages 59 | each of which is preceeded by a title - the titles each begin with two colons (::). 60 | Two of the passages in this code are special. The 61 | %code ::StoryTitle 62 | passage contains the name of our story and by convention it appears at or near the top 63 | of the file. The 64 | %code ::Start 65 | passage is 66 | %em usually 67 | the one that the player reads first, when they play your game. 68 | %p 69 | Save your file as 70 | = succeed '.' do 71 | %code escape-from-earth.tw2 72 | You can use any file extension you like, but conventionally Twee2 files have a 73 | %code .tw2 74 | suffix to differentiate them from other types of files. If you're using Notepad for 75 | Windows, you might need to select "All files" from the "Save as Type" drop-down while 76 | saving, or else Windows will put 77 | %code .txt 78 | on the end of the filename. 79 | %p 80 | To compile your code, open a command prompt or terminal in the same directory as your code file, and run: 81 | %p 82 | %pre 83 | twee2 build escape-from-earth.tw2 escape-from-earth.html 84 | %p 85 | Twee2 will compile a HTML output file based on the code. Open this HTML file in your preferred web browser 86 | e.g. by double-clicking on it, and you can play your new story. Or 87 | %a{href: 'escape-from-earth.html'} play it online 88 | to discover what you might expect to see. 89 | %p 90 | This short story is a very simple adventure. It might be easiest to describe in terms of a flowchart: 91 | %p 92 | %img{src: 'images/escape-from-earth.png', class: 'img-responsive', alt: 'Flowchart showing the seven possible endings to Escape From Earth.'} 93 | %p 94 | In each passage, the player may click a link to be taken to a different passage. Links appear in the code 95 | as some text, an arrow 96 | = surround '(', '),' do 97 | %code -> 98 | and the name of the passage that the player will be taken to upon clicking it. So the code 99 | %code [[Read the book->Read Book]] 100 | would show the text "Read the book" to the player, and if they click it they'll be taken to the passage that 101 | begins with 102 | = succeed '.' do 103 | %code ::Read Book 104 | You can use Markdown formatting to style your text. Note the asterisks around the words "THE END" in 105 | = succeed ':' do 106 | %em Escape from Earth 107 | these make that text appear in bold. 108 | 109 | .col-sm-6 110 | %pre= File::read 'source/escape-from-earth.tw2' 111 | 112 | .row 113 | .col-sm-12 114 | %h2 4. Start playing! 115 | %p 116 | You now know enough to start making simple 'choose your own adventure'-type stories with Twee2. 117 | For more-detailed information, see 118 | = succeed '.' do 119 | %a{href: 'documentation.html'} the full documentation 120 | -------------------------------------------------------------------------------- /web/build/install.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Twee2 7 | | Install 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 46 |
47 |
48 |
49 |

Installing Twee2

50 |

Prerequisites

51 |

52 | Twee2 requires 53 | Ruby 54 | version 2.0 or higher. You can check whether Ruby is installed and what version it is 55 | by opening up a command prompt or terminal and running: 56 |

57 |
ruby -v
58 |

59 | If you don't have Ruby installed, there are several ways to easily install it. Here are some quick ways: 60 |

61 |
62 |
63 |
64 |
65 |
66 |

Windows

67 |

68 | Download RubyInstaller 69 |

70 |

71 | Note that the 'decompile' feature, which turns Twine 2 story files into Twee2 source code, is not supported on Windows. 72 |

73 |
74 |
75 |
76 |
77 |

MacOS

78 |

79 | MacOS comes with Ruby, but it's usually out-of-date. 80 | 81 | Install a modern version 82 | 83 | using RVM. 84 |

85 |
86 |
87 |
88 |
89 |

Linux

90 |

91 | Many Linux distributions have packages for Ruby, but it's often out-of-date. 92 | Install 93 | RVM 94 | and use it to get Ruby 2.1.6 by running: 95 |

96 | gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \curl -sSL https://get.rvm.io | bash -s stable --ruby=2.1.6 rvm use 2.1.6 --default 97 |
98 |
99 |
100 |
101 |
102 |

Installing Twee2

103 |

104 | To install Twee2, open a command prompt or terminal and type: 105 |

106 |
gem install twee2
107 |

108 | You can check that it's installed correctly by running: 109 |

110 |
twee2
111 |

112 | Now you're ready to dive into the 113 | two-minute tutorial 114 | or the 115 | full documentation. 116 |

117 |
118 |
119 |
120 |
121 |

Uninstalling Twee2

122 |

123 | If you ever want to uninstall Twee2, simply run: 124 |

125 |
gem uninstall twee2
126 |
127 |
128 |

Source code

129 |

130 | Twee2 is open-source, which means you can read and change its code for yourself. 131 | Get the code on GitHub. 132 |

133 |
134 |
135 | 160 |
161 | 162 | 163 | -------------------------------------------------------------------------------- /web/build/escape-from-earth.tw2: -------------------------------------------------------------------------------- 1 | ::StoryTitle 2 | Escape from Earth 3 | 4 | ::Start 5 | There are only hours left before the asteroid hits and, if the news is to be believed, all life on Earth is extinguished. The spaceship is fuelled-up and [[ready to go->Ready]], but after all the rush of the last few weeks, you don't feel like you've had a chance to say goodbye to the planet that's been your home your entire life. [[Your father->Father]] glances over to check that you're heading into the vessel as he shepherds [[the family dog->Dog]]'s out of the hangar door. Behind the barriers, [[your sister->Sister]] gives a sad wave, clutching a photo in her hand. 6 | 7 | ::Ready 8 | You've still got a few seconds before you absolutely have to [[get on board->Board]], if you'd like to go and [[help your father->Father]], say goodbye to [[the family dog->Dog]], or check up on [[your sister->Sister]], who you're having to leave behind. 9 | 10 | ::Board 11 | No, you decide, the time for goodbyes has passed. After your weeks of preparation for the journey through space you don't feel the need to tie yourself to Earth any more-strongly than you have to: it's already hard enough to let go. One at a time, you climb the steps into the rocket ship and find your seat. You close the blind that cover your tiny circular window and take a deep breath. Letting go isn't easy, but there's no reason you should make it harder for yourself. 12 | 13 | **THE END** 14 | 15 | ::Father 16 | You run to help your father with the dog, trying in vain to keep your mind occupied by anything but the evacuation. Wordlessly, you walk together to the door and hand over the dog's lead to the armed guard there. For a moment, you feel bad about leaving him with a stranger, but in six hours it won't matter anyway. Looking out through the hangar door, you feel a momentary pang of jealousy for those left behind: they get to [[watch one final sunrise together->Sunrise]]... meanwhile, you and the other "lucky" ten-thousand survivors will be [[in orbit->Orbit]], weightless but still weighed-down by your grief over the billions of deaths below. 17 | 18 | ::Sunrise 19 | It's an impulsive moment and you might not live to regret it, but right now, it's the right choice: as the guard is distracted by the dog, you duck under the barrier and run out into the cold night. Minutes later, you hear the deafening boom of the rocket lifting off, and you know that you're among those left on Earth. You find your sister and your mother and exchange hugs. Then, in quiet contemplation, you sit and watch your final sunrise as the first meteor fragments graze the sky. 20 | 21 | **THE END** 22 | 23 | ::Orbit 24 | Who is making the greater sacrifice, you wonder? Those billions who will die here on Earth, today, or the ten thousand evacuees who are to make their way to the Martian colony? But it's about more than sacrifice: it's about duty. Your age, gender, and health made you eligible for the lottery; your father was chosen for his expertise as a scientist. Now you owe it to your species and to those you leave behind to continue humankind's legacy. The rocket blasts off, and you look back through the window to see the lights of the planet below, artificially-lit for the last time. 25 | 26 | **THE END** 27 | 28 | ::Dog 29 | The old family dog's been your faithful friend for years, and it feels wrong to leave without saying goodbye. You almost envy him that he doesn't know what's coming for him and all of the humans that will be left behind. Suddenly, you're [[overcome with emotion->Emotion]]: everything you'd pushed down inside you since you heard the news that you'd won the evacuation lottery, last month, comes welling to the surface. Your father wraps his arms around your shoulders and gives you [[a squeeze of support->Support]], bringing your focus back to what must be done. 30 | 31 | ::Emotion 32 | It's no use trying to hide it any longer. You can't hold back the tears. Each wave of grief is followed by a wave of guilt: why should it be you that survives when so many others face almost-certain death? And then, once again, grief at the loss of everything you know and love. Your legs buckle, and the dog tries to comfort you, but he doesn't understand. It's your father who eventually helps you to your feet and back towards the rocket ship. You can't bring yourself to look back as you climb the steps, using all of your emotional energy just to make it onto the craft. You'll never recover from this suffering, you're sure of it. 33 | 34 | **THE END** 35 | 36 | ::Support 37 | "Goodbye, boy," you say, scratching the dog behind the ears. He looks up at you with concern in his eyes, sensing your discomfort, and you try to make your voice as reassuring as possible. What does it say about you, you wonder, that your last words on Earth were to an animal? Later, in the shuttle, you'll lose the last of your emotional reserves and break down: but for now, you need to be strong. You are the face of hope for the future of the human race, and it's time to take your place on Mars. 38 | 39 | **THE END** 40 | 41 | ::Sister 42 | In violation of the rules you were told as you entered the hangar, you turn and sprint across to your sister at the barriers. Reaching over the fence and with tears in your eyes, you give her one last hug. You try [[not to think->Hold On]] about the fact that this time, when you let go, you'll be letting go forever. She clutches you tight, and you can feel [[the photo->Photo]] in her hand scrape against the back of your neck. 43 | 44 | ::Hold On 45 | You're still locked in an embrace when the armed guard comes and separates you. You know that you shouldn't have, but you couldn't have left without saying that one last goodbye. You're shepherded swiftly onto the spaceship, and you're disappointed to disover that your window doesn't look in the direction of the crowd. You'd had this romantic idea that you'd be able to look back on your sister as you took off, but it wasn't to be. The rockets ingnite, and you take a deep breath. Here begins the first chapter of your new life, and ends the last chapter of your old one. 46 | 47 | **THE END** 48 | 49 | ::Photo 50 | "I wanted you to take this," your sister says, thrusting the photo into your hands. You recognise the photo: you took it at arms-reach to capture the pair of you, sitting out on the grass under a starry sky while, in the background, your father shows off a telescope to a group of onlookers. You stuff it into your pocket before the security guard arrives to shepherd you back towards the rocket ship. Weeks later, you'll frame it in your new home on Mars, and it will come to represent everything that you left behind in order to be there. But right now, all you want is one final hug before takeoff. 51 | 52 | **THE END** 53 | -------------------------------------------------------------------------------- /web/source/escape-from-earth.tw2: -------------------------------------------------------------------------------- 1 | ::StoryTitle 2 | Escape from Earth 3 | 4 | ::Start 5 | There are only hours left before the asteroid hits and, if the news is to be believed, all life on Earth is extinguished. The spaceship is fuelled-up and [[ready to go->Ready]], but after all the rush of the last few weeks, you don't feel like you've had a chance to say goodbye to the planet that's been your home your entire life. [[Your father->Father]] glances over to check that you're heading into the vessel as he shepherds [[the family dog->Dog]]'s out of the hangar door. Behind the barriers, [[your sister->Sister]] gives a sad wave, clutching a photo in her hand. 6 | 7 | ::Ready 8 | You've still got a few seconds before you absolutely have to [[get on board->Board]], if you'd like to go and [[help your father->Father]], say goodbye to [[the family dog->Dog]], or check up on [[your sister->Sister]], who you're having to leave behind. 9 | 10 | ::Board 11 | No, you decide, the time for goodbyes has passed. After your weeks of preparation for the journey through space you don't feel the need to tie yourself to Earth any more-strongly than you have to: it's already hard enough to let go. One at a time, you climb the steps into the rocket ship and find your seat. You close the blind that cover your tiny circular window and take a deep breath. Letting go isn't easy, but there's no reason you should make it harder for yourself. 12 | 13 | **THE END** 14 | 15 | ::Father 16 | You run to help your father with the dog, trying in vain to keep your mind occupied by anything but the evacuation. Wordlessly, you walk together to the door and hand over the dog's lead to the armed guard there. For a moment, you feel bad about leaving him with a stranger, but in six hours it won't matter anyway. Looking out through the hangar door, you feel a momentary pang of jealousy for those left behind: they get to [[watch one final sunrise together->Sunrise]]... meanwhile, you and the other "lucky" ten-thousand survivors will be [[in orbit->Orbit]], weightless but still weighed-down by your grief over the billions of deaths below. 17 | 18 | ::Sunrise 19 | It's an impulsive moment and you might not live to regret it, but right now, it's the right choice: as the guard is distracted by the dog, you duck under the barrier and run out into the cold night. Minutes later, you hear the deafening boom of the rocket lifting off, and you know that you're among those left on Earth. You find your sister and your mother and exchange hugs. Then, in quiet contemplation, you sit and watch your final sunrise as the first meteor fragments graze the sky. 20 | 21 | **THE END** 22 | 23 | ::Orbit 24 | Who is making the greater sacrifice, you wonder? Those billions who will die here on Earth, today, or the ten thousand evacuees who are to make their way to the Martian colony? But it's about more than sacrifice: it's about duty. Your age, gender, and health made you eligible for the lottery; your father was chosen for his expertise as a scientist. Now you owe it to your species and to those you leave behind to continue humankind's legacy. The rocket blasts off, and you look back through the window to see the lights of the planet below, artificially-lit for the last time. 25 | 26 | **THE END** 27 | 28 | ::Dog 29 | The old family dog's been your faithful friend for years, and it feels wrong to leave without saying goodbye. You almost envy him that he doesn't know what's coming for him and all of the humans that will be left behind. Suddenly, you're [[overcome with emotion->Emotion]]: everything you'd pushed down inside you since you heard the news that you'd won the evacuation lottery, last month, comes welling to the surface. Your father wraps his arms around your shoulders and gives you [[a squeeze of support->Support]], bringing your focus back to what must be done. 30 | 31 | ::Emotion 32 | It's no use trying to hide it any longer. You can't hold back the tears. Each wave of grief is followed by a wave of guilt: why should it be you that survives when so many others face almost-certain death? And then, once again, grief at the loss of everything you know and love. Your legs buckle, and the dog tries to comfort you, but he doesn't understand. It's your father who eventually helps you to your feet and back towards the rocket ship. You can't bring yourself to look back as you climb the steps, using all of your emotional energy just to make it onto the craft. You'll never recover from this suffering, you're sure of it. 33 | 34 | **THE END** 35 | 36 | ::Support 37 | "Goodbye, boy," you say, scratching the dog behind the ears. He looks up at you with concern in his eyes, sensing your discomfort, and you try to make your voice as reassuring as possible. What does it say about you, you wonder, that your last words on Earth were to an animal? Later, in the shuttle, you'll lose the last of your emotional reserves and break down: but for now, you need to be strong. You are the face of hope for the future of the human race, and it's time to take your place on Mars. 38 | 39 | **THE END** 40 | 41 | ::Sister 42 | In violation of the rules you were told as you entered the hangar, you turn and sprint across to your sister at the barriers. Reaching over the fence and with tears in your eyes, you give her one last hug. You try [[not to think->Hold On]] about the fact that this time, when you let go, you'll be letting go forever. She clutches you tight, and you can feel [[the photo->Photo]] in her hand scrape against the back of your neck. 43 | 44 | ::Hold On 45 | You're still locked in an embrace when the armed guard comes and separates you. You know that you shouldn't have, but you couldn't have left without saying that one last goodbye. You're shepherded swiftly onto the spaceship, and you're disappointed to disover that your window doesn't look in the direction of the crowd. You'd had this romantic idea that you'd be able to look back on your sister as you took off, but it wasn't to be. The rockets ingnite, and you take a deep breath. Here begins the first chapter of your new life, and ends the last chapter of your old one. 46 | 47 | **THE END** 48 | 49 | ::Photo 50 | "I wanted you to take this," your sister says, thrusting the photo into your hands. You recognise the photo: you took it at arms-reach to capture the pair of you, sitting out on the grass under a starry sky while, in the background, your father shows off a telescope to a group of onlookers. You stuff it into your pocket before the security guard arrives to shepherd you back towards the rocket ship. Weeks later, you'll frame it in your new home on Mars, and it will come to represent everything that you left behind in order to be there. But right now, all you want is one final hug before takeoff. 51 | 52 | **THE END** 53 | -------------------------------------------------------------------------------- /lib/twee2/story_file.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'haml' 3 | require 'coffee_script' 4 | require 'sass' 5 | require 'builder' 6 | 7 | module Twee2 8 | class StoryFileNotFoundException < Exception; end 9 | 10 | class StoryFile 11 | attr_accessor :passages 12 | attr_reader :child_story_files 13 | 14 | HAML_OPTIONS = { 15 | remove_whitespace: true 16 | } 17 | Tilt::CoffeeScriptTemplate.default_bare = true # bare mode for HAML :coffeescript blocks 18 | COFFEESCRIPT_OPTIONS = { 19 | bare: true 20 | } 21 | 22 | # Loads the StoryFile with the given name 23 | def initialize(filename) 24 | raise(StoryFileNotFoundException) if !File::exists?(filename) 25 | @passages, current_passage = {}, nil 26 | @child_story_files = [] 27 | 28 | # Load file into memory to begin with 29 | lines = File::read(filename).split(/\r?\n/) 30 | # First pass - go through and perform 'includes' 31 | i, in_story_includes_section = 0, false 32 | while i < lines.length 33 | line = lines[i] 34 | if line =~ /^:: *StoryIncludes */ 35 | in_story_includes_section = true 36 | elsif line =~ /^::(?!@)/ 37 | in_story_includes_section = false 38 | elsif in_story_includes_section && (line.strip != '') 39 | child_file = line.strip 40 | # include a file here because we're in the StoryIncludes section 41 | if File::exists?(child_file) 42 | lines.push(*File::read(child_file).split(/\r?\n/)) # add it on to the end 43 | child_story_files.push(child_file) 44 | else 45 | puts "WARNING: tried to include file '#{line.strip}' via StoryIncludes but file was not found." 46 | end 47 | elsif line =~ /^( *)::@include (.*)$/ 48 | # include a file here because an @include directive was spotted 49 | prefix, filename = $1, $2.strip 50 | if File::exists?(filename) 51 | lines[i,1] = File::read(filename).split(/\r?\n/).map{|l|"#{prefix}#{l}"} # insert in-place, with prefix of appropriate amount of whitespace 52 | i-=1 # process this line again, in case of ::@include nesting 53 | else 54 | puts "WARNING: tried to ::@include file '#{filename}' but file was not found." 55 | end 56 | end 57 | i+=1 58 | end 59 | # Second pass - parse the file 60 | lines.each do |line| 61 | if line =~ /^:: *([^\[]*?) *(\[(.*?)\])? *(<(.*?)>)? *$/ 62 | @passages[current_passage = $1.strip] = { tags: ($3 || '').split(' '), position: $5, content: '', exclude_from_output: false, pid: nil} 63 | elsif current_passage 64 | @passages[current_passage][:content] << "#{line}\n" 65 | end 66 | end 67 | @passages.each_key{|k| @passages[k][:content].strip!} # Strip excessive trailing whitespace 68 | # Run each passage through a preprocessor, if required 69 | run_preprocessors 70 | # Extract 'special' passages and mark them as not being included in output 71 | story_css, pid, @story_js, @story_start_pid, @story_start_name = '', 0, '', nil, 'Start' 72 | @passages.each_key do |k| 73 | if k == 'StoryTitle' 74 | Twee2::build_config.story_name = @passages[k][:content] 75 | @passages[k][:exclude_from_output] = true 76 | elsif k == 'StoryIncludes' 77 | @passages[k][:exclude_from_output] = true # includes should already have been handled above 78 | elsif @passages[k][:tags].include? 'stylesheet' 79 | story_css << "#{@passages[k][:content]}\n" 80 | @passages[k][:exclude_from_output] = true 81 | elsif @passages[k][:tags].include? 'script' 82 | @story_js << "#{@passages[k][:content]}\n" 83 | @passages[k][:exclude_from_output] = true 84 | elsif @passages[k][:tags].include? 'twee2' 85 | eval @passages[k][:content] 86 | @passages[k][:exclude_from_output] = true 87 | else 88 | @passages[k][:pid] = (pid += 1) 89 | end 90 | end 91 | @story_start_pid = (@passages[@story_start_name] || {pid: 1})[:pid] 92 | # Generate XML in Twine 2 format 93 | @story_data = Builder::XmlMarkup.new 94 | # TODO: what is tw-storydata's "options" attribute for? 95 | @story_data.tag!('tw-storydata', { 96 | name: Twee2::build_config.story_name, 97 | startnode: @story_start_pid, 98 | creator: 'Twee2', 99 | 'creator-version' => Twee2::VERSION, 100 | ifid: Twee2::build_config.story_ifid, 101 | format: '{{STORY_FORMAT}}', 102 | options: '' 103 | }) do 104 | @story_data.style(story_css, role: 'stylesheet', id: 'twine-user-stylesheet', type: 'text/twine-css') 105 | @story_data.script('{{STORY_JS}}', role: 'script', id: 'twine-user-script', type: 'text/twine-javascript') 106 | @passages.each do |k,v| 107 | unless v[:exclude_from_output] 108 | @story_data.tag!('tw-passagedata', { pid: v[:pid], name: k, tags: v[:tags].join(' '), position: v[:position] }, v[:content]) 109 | end 110 | end 111 | end 112 | end 113 | 114 | # Returns the rendered XML that represents this story 115 | def xmldata 116 | data = @story_data.target! 117 | data.gsub('{{STORY_JS}}', @story_js) 118 | end 119 | 120 | # Runs HAML, Coffeescript etc. preprocessors across each applicable passage 121 | def run_preprocessors 122 | @passages.each_key do |k| 123 | # HAML 124 | if @passages[k][:tags].include? 'haml' 125 | @passages[k][:content] = Haml::Engine.new(@passages[k][:content], HAML_OPTIONS).render 126 | @passages[k][:tags].delete 'haml' 127 | end 128 | # Coffeescript 129 | if @passages[k][:tags].include? 'coffee' 130 | @passages[k][:content] = CoffeeScript.compile(@passages[k][:content], COFFEESCRIPT_OPTIONS) 131 | @passages[k][:tags].delete 'coffee' 132 | end 133 | # SASS / SCSS 134 | if @passages[k][:tags].include? 'sass' 135 | @passages[k][:content] = Sass::Engine.new(@passages[k][:content], :syntax => :sass).render 136 | end 137 | if @passages[k][:tags].include? 'scss' 138 | @passages[k][:content] = Sass::Engine.new(@passages[k][:content], :syntax => :scss).render 139 | end 140 | end 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /web/source/index.html.haml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Interactive Fiction for Power Users 3 | --- 4 | 5 | .page-header 6 | .row.jumbotron 7 | .col-sm-8.col-xs-12 8 | %h1 Twee2 9 | .lead 10 | Interactive Fiction for Power Users, powered by 11 | %a{href: 'http://twinery.org'} Twine 12 | %ul.list-inline{style: 'line-height: 4em;'} 13 | %li 14 | %a.btn.btn-primary.btn-lg{role: 'button', href: 'install.html'} 15 | %span.glyphicon.glyphicon-cloud-download{aria: { hidden: true }} 16 | Install Twee2 17 | %li 18 | %a.btn.btn-default.btn-lg{role: 'button', href: 'tutorial.html'} 19 | %span.glyphicon.glyphicon-time{aria: { hidden: true }} 20 | 2-minute tutorial 21 | %li 22 | %a.btn.btn-default.btn-lg{role: 'button', href: 'documentation.html'} 23 | %span.glyphicon.glyphicon-book{aria: { hidden: true }} 24 | Documentation 25 | %li 26 | %a.btn.btn-default.btn-lg{role: 'button', href: 'https://github.com/Dan-Q/twee2'} 27 | %span.glyphicon.glyphicon-link{aria: { hidden: true }} 28 | Source 29 | .col-sm-4.hidden-xs 30 | %img.img-responsive{src: 'images/twee2-logo.png', alt: 'Twee2 Logo'} 31 | 32 | .row 33 | .col-sm-12 34 | %h2 What is Twee2? 35 | %p 36 | Twee2 is a system for writing web-based interactive fiction in the 37 | %a{href: 'https://en.wikipedia.org/wiki/Choose_Your_Own_Adventure', title: 'Wikipedia article: Choose Your Own Adventure'} "Choose Your Own Adventure" 38 | style (but it can do a lot more than that, too). It's built on top of 39 | = succeed ',' do 40 | %a{href: 'http://twinery.org', title: 'Twine'} Twine 41 | but instead of Twine's graphical editing system Twee2 works with your favourite text editor and 42 | tools, making it more-suitable for power users, advanced Twine authors, and those with a programming background. 43 | 44 | %h2 What does it look like? 45 | 46 | .row 47 | .col-md-6 48 | .panel.panel-primary 49 | .panel-heading Code 50 | .panel-body 51 | %pre 52 | :preserve 53 | ::StoryTitle 54 | Choosing Your Own CYOA 55 | 56 | ::Start 57 | ### Choose Your Own CYOA 58 | #### A demonstrative adventure 59 | 60 | You sit at your computer and contemplate how you're going to 61 | write your next HTML adventure game. You wrote your last one 62 | [using Twine->Twine], but you've heard good things about 63 | [Twee2], as well. 64 | 65 | ::Twine 66 | You decide to stick with what you know and use Twine. That's 67 | fine: it's a great system and it's easy to use, especially 68 | for beginners. Maybe next time you'll be ready to try Twee2! 69 | 70 | **THE END** 71 | 72 | [Play again?->Start] 73 | 74 | ::Twee2 75 | You decide to write your latest adventure using Twee2. You 76 | find that you're able to use all of the things you're 77 | familiar with from Twine, but you also get to use your 78 | favourite text editor, break up your work into multiple 79 | files, mark up content using modern languages, and preview 80 | in a separate browser while you work. 81 | 82 | **THE END** 83 | 84 | [Play again?->Start] 85 | 86 | .col-md-6 87 | .panel.panel-primary 88 | .panel-heading Output 89 | .panel-body 90 | .embed-responsive.embed-responsive-4by3 91 | %iframe.embed-responsive-item{src: 'eg1.html'} 92 | 93 | .row 94 | .col-sm-12 95 | %h2 How does it compare to Twine? 96 | %p 97 | Twee2 and Twine 2 produce 98 | %em the same 99 | output. They differ only in how you, the author, work to produce that output: 100 | 101 | %table.table.table-bordered 102 | %thead 103 | %tr 104 | %th.warning 105 | Feature 106 | %th 107 | Twine 2 108 | %th.success 109 | Twee2 110 | %tbody 111 | %tr 112 | %th.warning.text-right Editor 113 | %td Web-based "drag-and-drop" editor 114 | %td.success Your favourite text editor 115 | %tr 116 | %th.warning.text-right Story formats supported 117 | %td Harlowe, Paperthin, Snowman, SugarCube, and any custom ones you add 118 | %td.success Harlowe, Paperthin, Snowman, SugarCube, and any custom ones you add 119 | %tr 120 | %th.warning.text-right Files saved to... 121 | %td Twine 2's own store (can be exported elsewhere) 122 | %td.success Wherever you like 123 | %tr 124 | %th.warning.text-right Inputs 125 | %td Twine 2 files 126 | %td.success Twee2 files, Twee files, Twine 2 files (except on Windows) 127 | %tr 128 | %th.warning.text-right Outputs 129 | %td Twine 2 web pages 130 | %td.success Twine 2 web pages 131 | %tr 132 | %th.warning.text-right Supported formatting languages 133 | %td Markdown 134 | %td.success Markdown, HAML 135 | %tr 136 | %th.warning.text-right Supported additional languages 137 | %td CSS, Javascript 138 | %td.success CSS, Javascript, SASS/SCSS, Coffeescript 139 | %tr 140 | %th.warning.text-right Simultaneous working 141 | %td No 142 | %td.success Yes, using includes and/or source control 143 | %tr 144 | %th.warning.text-right Source control support 145 | %td No/barely 146 | %td.success Yes, use your favourite source control 147 | %tr 148 | %th.warning.text-right Can split work into multiple files 149 | %td No 150 | %td.success Yes 151 | %tr 152 | %th.warning.text-right Regeneration-on-save 153 | %td No 154 | %td.success Yes, with 'watch' command 155 | 156 | %ul 157 | %li 158 | Twine is better for people new to writing interactive fiction and to programming 159 | in general, or those who prefer a visual interface. 160 | %li 161 | Twee2 is better for people experienced in writing interactive fiction and/or other 162 | kinds of programming, or who prefer a text interface. 163 | 164 | .row 165 | .col-md-8.col-md-offset-2 166 | .well 167 | %h3 Get started! 168 | %ul.list-inline{style: 'line-height: 4em;'} 169 | %li 170 | %a.btn.btn-primary.btn-lg{role: 'button', href: 'install.html'} 171 | %span.glyphicon.glyphicon-cloud-download{aria: { hidden: true }} 172 | Install Twee2 173 | %li 174 | %a.btn.btn-default.btn-lg{role: 'button', href: 'tutorial.html'} 175 | %span.glyphicon.glyphicon-time{aria: { hidden: true }} 176 | 2-minute tutorial 177 | %li 178 | %a.btn.btn-default.btn-lg{role: 'button', href: 'documentation.html'} 179 | %span.glyphicon.glyphicon-book{aria: { hidden: true }} 180 | Documentation 181 | %li 182 | %a.btn.btn-default.btn-lg{role: 'button', href: 'https://github.com/Dan-Q/twee2'} 183 | %span.glyphicon.glyphicon-link{aria: { hidden: true }} 184 | Source 185 | -------------------------------------------------------------------------------- /storyFormats/Paperthin/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Twee2 7 | | Interactive Fiction for Power Users 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 46 |
47 | 87 |
88 |
89 |

What is Twee2?

90 |

91 | Twee2 is a system for writing web-based interactive fiction in the 92 | "Choose Your Own Adventure" 93 | style (but it can do a lot more than that, too). It's built on top of 94 | Twine, 95 | but instead of Twine's graphical editing system Twee2 works with your favourite text editor and 96 | tools, making it more-suitable for power users, advanced Twine authors, and those with a programming background. 97 |

98 |

What does it look like?

99 |
100 |
101 |
102 |
103 |
104 |
Code
105 |
106 |
::StoryTitle
Choosing Your Own CYOA

::Start
### Choose Your Own CYOA
#### A demonstrative adventure

You sit at your computer and contemplate how you're going to
write your next HTML adventure game. You wrote your last one
[using Twine->Twine], but you've heard good things about
[Twee2], as well.

::Twine
You decide to stick with what you know and use Twine. That's
fine: it's a great system and it's easy to use, especially
for beginners. Maybe next time you'll be ready to try Twee2!

**THE END**

[Play again?->Start]

::Twee2
You decide to write your latest adventure using Twee2. You
find that you're able to use all of the things you're
familiar with from Twine, but you also get to use your
favourite text editor, break up your work into multiple
files, mark up content using modern languages, and preview
in a separate browser while you work.

**THE END**

[Play again?->Start]
107 |
108 |
109 |
110 |
111 |
112 |
Output
113 |
114 |
115 | 116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |

How does it compare to Twine?

124 |

125 | Twee2 and Twine 2 produce 126 | the same 127 | output. They differ only in how you, the author, work to produce that output: 128 |

129 | 130 | 131 | 132 | 135 | 138 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 |
133 | Feature 134 | 136 | Twine 2 137 | 139 | Twee2 140 |
EditorWeb-based "drag-and-drop" editorYour favourite text editor
Story formats supportedHarlowe, Paperthin, Snowman, SugarCube, and any custom ones you addHarlowe, Paperthin, Snowman, SugarCube, and any custom ones you add
Files saved to...Twine 2's own store (can be exported elsewhere)Wherever you like
InputsTwine 2 filesTwee2 files, Twee files, Twine 2 files (except on Windows)
OutputsTwine 2 web pagesTwine 2 web pages
Supported formatting languagesMarkdownMarkdown, HAML
Supported additional languagesCSS, JavascriptCSS, Javascript, SASS/SCSS, Coffeescript
Simultaneous workingNoYes, using includes and/or source control
Source control supportNo/barelyYes, use your favourite source control
Can split work into multiple filesNoYes
Regeneration-on-saveNoYes, with 'watch' command
201 |
    202 |
  • 203 | Twine is better for people new to writing interactive fiction and to programming 204 | in general, or those who prefer a visual interface. 205 |
  • 206 |
  • 207 | Twee2 is better for people experienced in writing interactive fiction and/or other 208 | kinds of programming, or who prefer a text interface. 209 |
  • 210 |
211 |
212 |
213 |
214 |
215 |
216 |

Get started!

217 | 243 |
244 |
245 |
246 | 271 |
272 | 273 | 274 | -------------------------------------------------------------------------------- /web/build/tutorial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Twee2 7 | | Start writing Interactive Fiction in two minutes 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 46 |
47 |
48 |
49 |

2-minute tutorial

50 |

51 | You can write your first piece of interactive fiction in just two minutes, after you've installed 52 | Twee2. Just follow the steps below. When you're ready to move on to more-complicated adventures, 53 | see the 54 | full documentation. 55 |

56 |
57 |
58 |
59 |
60 |

1. Install Twee2

61 |

62 | If you haven't 63 | installed Twee2 64 | yet, do that first. 65 |

66 |
67 | 73 |
74 |
75 |
76 |

2. Open your text editor

77 |

78 | You can write code in any text editor: your computer will almost-certainly already have one installed. 79 | On Windows, you can use Notepad. On MacOS, TextEdit is available (be sure to select "Make Plain Text" 80 | from the Format menu). On Linux, you might use GEdit, Kate, Emacs, Vim, Pico, or something else entirely. 81 |

82 |

83 | If you have a favourite text editor, you can use that: personally, I like 84 | Sublime Text, 85 | which is available for a variety of platforms and supports 86 | source code highlighting for Twee2. 87 |

88 |
89 |
90 | Notepad for Windows 91 |
92 |
93 |
94 |
95 |

3. Write some code

96 |

97 | To begin with, copy-paste the code 98 | below 99 | 100 | into your text editor (or 101 | download it here 102 | - you might need to right-click and select "Save as..."). 103 | Later, you can experiment with changing this file. 104 |

105 |

106 | Notice that the file is broken up into several 107 | passages, 108 | each of which is preceeded by a title - the titles each begin with two colons (::). 109 | Two of the passages in this code are special. The 110 | ::StoryTitle 111 | passage contains the name of our story and by convention it appears at or near the top 112 | of the file. The 113 | ::Start 114 | passage is 115 | usually 116 | the one that the player reads first, when they play your game. 117 |

118 |

119 | Save your file as 120 | escape-from-earth.tw2. 121 | You can use any file extension you like, but conventionally Twee2 files have a 122 | .tw2 123 | suffix to differentiate them from other types of files. If you're using Notepad for 124 | Windows, you might need to select "All files" from the "Save as Type" drop-down while 125 | saving, or else Windows will put 126 | .txt 127 | on the end of the filename. 128 |

129 |

130 | To compile your code, open a command prompt or terminal in the same directory as your code file, and run: 131 |

132 |

133 |

twee2 build escape-from-earth.tw2 escape-from-earth.html
134 |

135 |

136 | Twee2 will compile a HTML output file based on the code. Open this HTML file in your preferred web browser 137 | e.g. by double-clicking on it, and you can play your new story. Or 138 | play it online 139 | to discover what you might expect to see. 140 |

141 |

142 | This short story is a very simple adventure. It might be easiest to describe in terms of a flowchart: 143 |

144 |

145 | Flowchart showing the seven possible endings to Escape From Earth. 146 |

147 |

148 | In each passage, the player may click a link to be taken to a different passage. Links appear in the code 149 | as some text, an arrow 150 | (->), 151 | and the name of the passage that the player will be taken to upon clicking it. So the code 152 | [[Read the book->Read Book]] 153 | would show the text "Read the book" to the player, and if they click it they'll be taken to the passage that 154 | begins with 155 | ::Read Book. 156 | You can use Markdown formatting to style your text. Note the asterisks around the words "THE END" in 157 | Escape from Earth: 158 | these make that text appear in bold. 159 |

160 |
161 |
162 |
::StoryTitle
Escape from Earth

::Start
There are only hours left before the asteroid hits and, if the news is to be believed, all life on Earth is extinguished. The spaceship is fuelled-up and [[ready to go->Ready]], but after all the rush of the last few weeks, you don't feel like you've had a chance to say goodbye to the planet that's been your home your entire life. [[Your father->Father]] glances over to check that you're heading into the vessel as he shepherds [[the family dog->Dog]]'s out of the hangar door. Behind the barriers, [[your sister->Sister]] gives a sad wave, clutching a photo in her hand.

::Ready
You've still got a few seconds before you absolutely have to [[get on board->Board]], if you'd like to go and [[help your father->Father]], say goodbye to [[the family dog->Dog]], or check up on [[your sister->Sister]], who you're having to leave behind.

::Board
No, you decide, the time for goodbyes has passed. After your weeks of preparation for the journey through space you don't feel the need to tie yourself to Earth any more-strongly than you have to: it's already hard enough to let go. One at a time, you climb the steps into the rocket ship and find your seat. You close the blind that cover your tiny circular window and take a deep breath. Letting go isn't easy, but there's no reason you should make it harder for yourself.

**THE END**

::Father
You run to help your father with the dog, trying in vain to keep your mind occupied by anything but the evacuation. Wordlessly, you walk together to the door and hand over the dog's lead to the armed guard there. For a moment, you feel bad about leaving him with a stranger, but in six hours it won't matter anyway. Looking out through the hangar door, you feel a momentary pang of jealousy for those left behind: they get to [[watch one final sunrise together->Sunrise]]... meanwhile, you and the other "lucky" ten-thousand survivors will be [[in orbit->Orbit]], weightless but still weighed-down by your grief over the billions of deaths below.

::Sunrise
It's an impulsive moment and you might not live to regret it, but right now, it's the right choice: as the guard is distracted by the dog, you duck under the barrier and run out into the cold night. Minutes later, you hear the deafening boom of the rocket lifting off, and you know that you're among those left on Earth. You find your sister and your mother and exchange hugs. Then, in quiet contemplation, you sit and watch your final sunrise as the first meteor fragments graze the sky.

**THE END**

::Orbit
Who is making the greater sacrifice, you wonder? Those billions who will die here on Earth, today, or the ten thousand evacuees who are to make their way to the Martian colony? But it's about more than sacrifice: it's about duty. Your age, gender, and health made you eligible for the lottery; your father was chosen for his expertise as a scientist. Now you owe it to your species and to those you leave behind to continue humankind's legacy. The rocket blasts off, and you look back through the window to see the lights of the planet below, artificially-lit for the last time.

**THE END**

::Dog
The old family dog's been your faithful friend for years, and it feels wrong to leave without saying goodbye. You almost envy him that he doesn't know what's coming for him and all of the humans that will be left behind. Suddenly, you're [[overcome with emotion->Emotion]]: everything you'd pushed down inside you since you heard the news that you'd won the evacuation lottery, last month, comes welling to the surface. Your father wraps his arms around your shoulders and gives you [[a squeeze of support->Support]], bringing your focus back to what must be done.

::Emotion
It's no use trying to hide it any longer. You can't hold back the tears. Each wave of grief is followed by a wave of guilt: why should it be you that survives when so many others face almost-certain death? And then, once again, grief at the loss of everything you know and love. Your legs buckle, and the dog tries to comfort you, but he doesn't understand. It's your father who eventually helps you to your feet and back towards the rocket ship. You can't bring yourself to look back as you climb the steps, using all of your emotional energy just to make it onto the craft. You'll never recover from this suffering, you're sure of it.

**THE END**

::Support
"Goodbye, boy," you say, scratching the dog behind the ears. He looks up at you with concern in his eyes, sensing your discomfort, and you try to make your voice as reassuring as possible. What does it say about you, you wonder, that your last words on Earth were to an animal? Later, in the shuttle, you'll lose the last of your emotional reserves and break down: but for now, you need to be strong. You are the face of hope for the future of the human race, and it's time to take your place on Mars.

**THE END**

::Sister
In violation of the rules you were told as you entered the hangar, you turn and sprint across to your sister at the barriers. Reaching over the fence and with tears in your eyes, you give her one last hug. You try [[not to think->Hold On]] about the fact that this time, when you let go, you'll be letting go forever. She clutches you tight, and you can feel [[the photo->Photo]] in her hand scrape against the back of your neck.

::Hold On
You're still locked in an embrace when the armed guard comes and separates you. You know that you shouldn't have, but you couldn't have left without saying that one last goodbye. You're shepherded swiftly onto the spaceship, and you're disappointed to disover that your window doesn't look in the direction of the crowd. You'd had this romantic idea that you'd be able to look back on your sister as you took off, but it wasn't to be. The rockets ingnite, and you take a deep breath. Here begins the first chapter of your new life, and ends the last chapter of your old one.

**THE END**

::Photo
"I wanted you to take this," your sister says, thrusting the photo into your hands. You recognise the photo: you took it at arms-reach to capture the pair of you, sitting out on the grass under a starry sky while, in the background, your father shows off a telescope to a group of onlookers. You stuff it into your pocket before the security guard arrives to shepherd you back towards the rocket ship. Weeks later, you'll frame it in your new home on Mars, and it will come to represent everything that you left behind in order to be there. But right now, all you want is one final hug before takeoff.

**THE END**
163 |
164 |
165 |
166 |
167 |

4. Start playing!

168 |

169 | You now know enough to start making simple 'choose your own adventure'-type stories with Twee2. 170 | For more-detailed information, see 171 | the full documentation. 172 |

173 |
174 |
175 | 200 |
201 | 202 | 203 | -------------------------------------------------------------------------------- /web/source/documentation.html.haml: -------------------------------------------------------------------------------- 1 | --- 2 | title: Full documentation 3 | --- 4 | 5 | .row 6 | .col-sm-3.col-sm-push-9 7 | %h2 Index 8 | %ul 9 | %li 10 | %a{href: '#relationship-to-twine'} Relationship to Twine 11 | %li 12 | %a{href: '#writing-code'} Writing and compiling code 13 | %li 14 | %a{href: '#story-formats'} Understanding story formats 15 | %li 16 | %a{href: '#twee2-syntax'} Twee2 syntax 17 | %ul 18 | %li 19 | %a{href: '#twee2-syntax-passages'} Passages 20 | %li 21 | %a{href: '#twee2-syntax-content'} Content 22 | %li 23 | %a{href: '#twee2-syntax-special-passages'} Special passages 24 | %li 25 | %a{href: '#includes'} Splitting your code into multiple files 26 | %li 27 | %a{href: '#build-config'} Setting build configuration options 28 | %li 29 | %a{href: '#decompiler'} Decompiling Twine 2 stories 30 | 31 | .col-sm-9.col-sm-pull-3 32 | %h1 Full documentation 33 | 34 | %h2#relationship-to-twine Relationship to Twine 35 | %p 36 | Twee2 is built on top of 37 | = succeed ',' do 38 | %a{href: 'http://twinery.org/'} Twine 2 39 | minus the graphical user interface. As a result, virtually all of the 40 | %a{href: 'http://twinery.org/wiki/twine2:guide'} documentation about Twine 2 41 | (and specifically the documentation about the 42 | = succeed ')' do 43 | %a{href: 'http://twinery.org/wiki/twine2:how_to_choose_a_story_format'} different story formats 44 | is relevant to Twee2, too. 45 | Because Twee2 is inspired by Twee, its documentation is also a valuable resource: however note that 46 | you can't use Twee 'macros' in Twee2. 47 | 48 | %h2#writing-code Writing and compiling code 49 | %p 50 | Write Twee2 code using your favourite text editor. Syntax highlighting is available via 51 | %a{href: 'https://github.com/monospaced/sublime-twee'} this Sublime Text plugin 52 | for Twee. By convention, Twee2 source files have the 53 | %code .tw2 54 | extension. 55 | %p 56 | Compile Twee2 files using the 57 | %code twee2 58 | command-line tool. There are two important modes of operation: 59 | %ul 60 | %li 61 | %code twee2 build input.tw2 output.html 62 | produces output.html based on the code in input.tw2 63 | %li 64 | %code twee2 watch input.tw2 output.html 65 | does the same thing, but 66 | %em watches 67 | input.tw2 for changes, automatically recompiling whenever it is updated (note that it's not 68 | smart enough to understand when 69 | %a{href: '#includes'} 'included' 70 | files have been changed, though). 71 | %p 72 | Because Twee2 source files are just plain text files, they're well-suited to use with 73 | source control systems: you could, for example, use 74 | %a{href: 'https://github.com/'} Github 75 | to share your code or to collaborate with other authors, even working on the same file. 76 | 77 | %h2#story-formats Understanding story formats 78 | %p 79 | Twee2 comes with built-in support for all of the same story formats that Twine 2 does: 80 | %ul 81 | %li 82 | %a{href: 'http://twine2.neocities.org/'} Harlowe 83 | \- the default format, which includes a simplified scripting format 84 | %li 85 | %a{href: 'https://bitbucket.org/klembot/snowman-2'} Snowman 86 | \- a minimal format that uses Underscore.js templates and jQuery to provide programmers with a powerful toolset 87 | %li 88 | %a{href: 'http://www.motoslave.net/sugarcube/'} SugarCube 89 | \- a TiddlyWiki-powered format that comes with support for multiple save 'slots' and a Twine 1/Twee 'macro'-like scripting syntax 90 | %li 91 | Paperthin - used when you select "View Proofing Copy" in Twine 2, this minimal skeleton isn't really an output format as it is a proofing tool 92 | %p 93 | If you've having difficulty choosing between them, there's 94 | %a{href: 'http://twinery.org/wiki/twine2:how_to_choose_a_story_format'} a summary of the differences 95 | in the Twine 2 documentation. 96 | %p 97 | To specify which format to use during compilation, use the optional 98 | %code --format 99 | parameter. E.g. you might type 100 | = succeed '.' do 101 | %code twee2 build input.tw2 output.html --format=Snowman 102 | You can get a list of the formats that Twee2 natively understands by running 103 | = succeed '.' do 104 | %code twee2 formats 105 | %p 106 | It's possible to use any story format, e.g. if you've downloaded or written your own, by specifying the path 107 | to the story format's directory (the one containing the format.js file) in your 108 | %code --format 109 | parameter. For example, you might run 110 | = succeed '.' do 111 | %code twee2 build input.tw2 output.html --format=./MyFormat 112 | %p 113 | Rather than setting the format on the command-line, it's possible to specify it within your source code 114 | itself, using 115 | %a{href: '#build-config'} build configuration 116 | options. 117 | 118 | %h2#twee2-syntax Twee2 syntax 119 | %p 120 | Twee2 uses a syntax that's heavily inspired-by but not 100% compatible with Twee: 121 | 122 | %h3#twee2-syntax-passages Passages 123 | %p 124 | Each block of text in Twee2 exists in a 125 | = succeed '.' do 126 | %em passage 127 | Each passage begins with a title, which is prefixed by two colons: 128 | %p 129 | %code ::My Passage Name 130 | %p 131 | Passage titles may only contain letters, numbers, basic punctuation, and spaces. Some authors prefer to avoid 132 | spaces in their passage titles. Passage titles are not case-sensitive: you don't have to use the same case when 133 | referring to a passage every time. Passages titles 134 | %em should 135 | be unique within a story: if they're not, only the last passage with a given title will be included in the story. 136 | %p 137 | Passage titles may optionally be suffixed by one or both of: 138 | %ul 139 | %li 140 | Any number of 141 | = succeed ',' do 142 | %em tags 143 | separated by spaced, inside a pair of square brackets. E.g: 144 | %br/ 145 | %code ::My Passage Name [tagone tagtwo] 146 | %br/ 147 | Tags can be used by code in your story (e.g. to send the player to a randomly-selected passage from a subset). 148 | = succeed ',' do 149 | %a{href: '#twee2-syntax-special-passages'} Some tags have special meanings 150 | as described below. 151 | %li 152 | A pair of coordinates, separated by a comma and enclosed within angle brackets. E.g.: 153 | %br/ 154 | %code ::My Passage Name <123,456> 155 | %br/ 156 | Coordinates have no meaning to Twee2, but they can be used to enhance compatability with Twine 2: Twine 2 uses 157 | these coordinates to decide where to show the passages in its WYSIWYG editor. 158 | %p 159 | If you include both tags and coordinates after a passage, the tags must come before the coordinates, e.g.: 160 | %p 161 | %code ::My Passage Name [tagone tagtwo] <123,456> 162 | %p 163 | Some passages and tags 164 | = succeed ',' do 165 | %a{href: '#twee2-syntax-special-passages'} have special meanings 166 | as described below 167 | 168 | %h3#twee2-syntax-content Content 169 | %p 170 | Each passage may contain any amount of content, most of which will be stuff that the player sees. Many 171 | story formats understand 172 | %em Markdown 173 | formatting in passages, which makes it possible to easily add formatting to your passages. Links to different 174 | passages are supported in any of the following syntaxes (where 'Dungeon' is the name of the passage they'll be 175 | transported if they click the link, and 'click here', where present, is the text that they'll see to click on): 176 | %ul 177 | %li 178 | %code [Dungeon] 179 | %li 180 | %code [click here->Dungeon] 181 | %li 182 | %code [Dungeon<-click here] 183 | %li 184 | %code [click here|Dungeon] 185 | 186 | %h2#twee2-syntax-special-passages Special passages 187 | %p 188 | Some passage names and tags have special meanings. These are: 189 | 190 | %h3 ::Start 191 | %p 192 | The 193 | %code ::Start 194 | passage will, by default, be used as the initial passage that your player sees when they start reading. 195 | You can override this using 196 | = succeed ',' do 197 | %a{href: '#build-config'} build configuration options 198 | but it's probably easier just to follow this convention. 199 | 200 | %h3 ::StorySubtitle, ::StoryAuthor, ::StoryMenu, and ::StorySettings 201 | %p 202 | These names were used for special passages in Twee 1. They're not used by Twee2, but to maintain compatability 203 | with Twee 1 they're ignored and you should avoid using them unless you're writing a story that needs to be 204 | capable of being compiled by both Twee 1 and Twee2. 205 | 206 | %h3 ::StoryIncludes 207 | %p 208 | Any 209 | %code ::StoryIncludes 210 | passages (you can have as many as you like, but you probably should avoid having more than one in any file in 211 | order to avoid confusion) are treated as lists of secondary Twee2 files to 'include' into your story. This is 212 | described in more detail below, under 213 | = succeed '.' do 214 | %a{href: '#includes'} splitting your code into multiple files 215 | 216 | %h3 [stylesheet] 217 | %p 218 | Any passages with the 'stylesheet' tag will not be included in your story, but their contents will be injected 219 | into the story's stylesheet. For example, you could write: 220 | %p 221 | %pre 222 | :preserve 223 | ::MyCoolStylesheet [stylesheet] 224 | 225 | body { 226 | background: #eee; 227 | } 228 | 229 | tw-passage tw-link { 230 | color: red; 231 | } 232 | %p 233 | It's also possible to use 234 | %a{href: 'http://sass-lang.com/'} SASS 235 | to enhance your stylesheet. Simply add the tag 'sass' or 'scss' to specify the dialect of SASS that you want 236 | to use, e.g.: 237 | %pre 238 | :preserve 239 | ::MyCoolStylesheet [scss stylesheet] 240 | 241 | body { 242 | background: #eee; 243 | } 244 | 245 | tw-passage { 246 | tw-link { 247 | color: red; 248 | } 249 | } 250 | 251 | %h3 [script] 252 | %p 253 | Any passages with the 'script' tag will not be included in your story, but their contents will be injected 254 | into the resulting web page as Javascript. For example, you could write: 255 | %p 256 | %pre 257 | :preserve 258 | ::SomeAwesomeCode [script] 259 | 260 | alert('This message will appear when the adventure starts!'); 261 | %p 262 | It's also possible to use 263 | %a{href: 'http://coffeescript.org/'} Coffeescript 264 | to make your Javascript development more-beautiful. Simply add the tag 'coffee' to your script block: 265 | %pre 266 | :preserve 267 | ::SomeAwesomeCode [coffee script] 268 | 269 | alert 'This message will appear when the adventure starts!' 270 | 271 | %h3 [haml] 272 | %p 273 | Put the 'haml' tag into your regular passages in order to allow you to write 274 | %a{href: 'http://haml.info/'} HAML 275 | code into your passages. HAML is a sophisticated templating language for producing HTML output, and 276 | for some it might be preferable to writing plain old Markdown. This includes the ability to inject 277 | Javascript/CoffeeScript into particular passages. For example, you could write: 278 | %p 279 | %pre 280 | :preserve 281 | ::NicksBar [haml] 282 | %p 283 | Nick's Bar is exactly the kind of nightspot that helps you remember why you quit drinking. 284 | A depressed-looking barman pours another beer for an equally depressed-looking drunk, while 285 | over in the corner a street thug plays with a knife as he eyes you up. The floor is sticky 286 | and the air reeks of stale alcohol. 287 | %p 288 | %strong What would you like to do? 289 | %ul 290 | %li [Nick<-Talk to the bartender] 291 | %li [BarToilet<-Go to the bathroom] 292 | %li [BarSit<-Sit in a booth] 293 | 294 | :coffeescript 295 | $ -> 296 | alert "This message will appear when you reach Nick's Bar!" 297 | 298 | %h3 [twee2] 299 | %p 300 | Passages marked with the 'twee2' tag are not included in your story. However, any Ruby code in them 301 | will be executed when the passage is processed by the builder. This can be used to 302 | %a{href: '#build-config'} set build configuration options 303 | as described below. 304 | 305 | %h2#includes Splitting your code into multiple files 306 | %p 307 | A major benefit of Twee2 over Twine 2 is that it's possible to break apart your story into multiple 308 | files, which can be used to structure your work, to facilitate teamworking with or without source 309 | control, and to make 'reusable' components such as stylesheets and scripts which can then be injected 310 | into later stories. There are two ways to do this: 311 | 312 | %h3 Using ::StoryIncludes 313 | %p 314 | Insert a 315 | %code ::StoryIncludes 316 | passage into your file and list within it the names of the files to include. Each will be appended 317 | to the end of your story before compilation. For example, the following game skeleton includes the 318 | contents of four other files - the main file contains only the 319 | %code ::Start 320 | passage: 321 | %p 322 | %pre 323 | :preserve 324 | ::Start 325 | **Journey To The Centre Of The Earth** 326 | 327 | by Dan Q 328 | 329 | [Play] 330 | 331 | ::StoryIncludes 332 | first_section.tw2 333 | second_section.tw2 334 | stylesheet.tw2 335 | javascripts.tw2 336 | 337 | %h3 Using ::@include directives 338 | %p 339 | You can put an 340 | %code ::@include 341 | directive anywhere in your code to insert the contents of another file at that point. 342 | %code ::@include 343 | respects your current indentation level (e.g. when using HAML), so it's safe to use at any indentation 344 | 'depth'. For example, the following passages each include a separate file that contains a description 345 | of the house in the distance: that description is shared between the two passages, and updating the 346 | file updates the description in both. 347 | %p 348 | %pre 349 | :preserve 350 | ::Greenhouse [haml] 351 | %p 352 | Light shines brightly through the tall glass walls of the ornate wrought-iron greenhouse, and 353 | the plants are verdant and lively. The [Garden<-door] stands open and a cool breeze blows in. 354 | %p 355 | ::@include description-of-house.tw2 356 | 357 | ::Garden [haml] 358 | %p 359 | The garden winds around decorative trees betwen the [greenhouse] and the [Porch<-mansion]. 360 | %p 361 | ::@include description-of-house.tw2 362 | 363 | %p 364 | Note that neither method of including code works recursively (i.e. you can't include a file that in 365 | turn includes another file), but this may be fixed in a future version. 366 | 367 | %h2#build-config Setting build configuration options 368 | %p 369 | Creating a passage with the 'twee2' tag can be used to run arbritrary Ruby code or to pass additional 370 | options to the Twee2 compiler. This expands the potential capability of Twee2 almost-limitlessly, but for now 371 | the only supported options are: 372 | %ul 373 | %li 374 | %strong 375 | %code 376 | = surround "Twee2::build_config.story_ifid = '", "'" do 377 | %em [your IFID] 378 | \- used to set the 379 | %a{href: 'http://www.ifwiki.org/index.php/IFID'} IFID of your story, to facilitate catologuing. If you 380 | don't include this directive, the compiler will suggest a random one to you every time you run it and 381 | explain how to add it: if you're going to publish your story publicly, you should add the line it 382 | suggests before you do so. 383 | %li 384 | %strong 385 | %code 386 | = surround "Twee2::build_config.story_format = '", "'" do 387 | %em [a story format] 388 | \- an alternative way of specifying the story format used by your story, rather than using the --format 389 | switch to the compiler. The syntax of format names and means of specifying externally-stored formats is 390 | identical to 391 | = succeed '.' do 392 | %a{href: '#story-formats'} using the --format switch 393 | %p 394 | For example: 395 | %p 396 | %pre 397 | :preserve 398 | ::Configuration [twee2] 399 | Twee2::build_config.story_ifid = '41AB7776-D198-40F5-BD54-0493D49DA58C' 400 | Twee2::build_config.story_format = 'Snowman' 401 | 402 | %h2#decompiler Decompiling Twine 2 stories 403 | %p 404 | It's possible to convert existing (compiled) Twee2/Twine 2 story files, in HTML format, back into 405 | Twee2 source files for further editing. This can be used to convert your Twine 2 projects into Twee2 406 | files or to easily examine the contents of somebody else's story. This feature does not work on Microsoft 407 | Windows. To use it, run: 408 | %p 409 | %code twee2 decompile input.html output.tw2 410 | %p 411 | The input HTML file can optionally be a full web URL. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /web/build/documentation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Twee2 7 | | Full documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 46 |
47 |
48 |
49 |

Index

50 | 84 |
85 |
86 |

Full documentation

87 |

Relationship to Twine

88 |

89 | Twee2 is built on top of 90 | Twine 2, 91 | minus the graphical user interface. As a result, virtually all of the 92 | documentation about Twine 2 93 | (and specifically the documentation about the 94 | different story formats) 95 | is relevant to Twee2, too. 96 | Because Twee2 is inspired by Twee, its documentation is also a valuable resource: however note that 97 | you can't use Twee 'macros' in Twee2. 98 |

99 |

Writing and compiling code

100 |

101 | Write Twee2 code using your favourite text editor. Syntax highlighting is available via 102 | this Sublime Text plugin 103 | for Twee. By convention, Twee2 source files have the 104 | .tw2 105 | extension. 106 |

107 |

108 | Compile Twee2 files using the 109 | twee2 110 | command-line tool. There are two important modes of operation: 111 |

112 |
    113 |
  • 114 | twee2 build input.tw2 output.html 115 | produces output.html based on the code in input.tw2 116 |
  • 117 |
  • 118 | twee2 watch input.tw2 output.html 119 | does the same thing, but 120 | watches 121 | input.tw2 for changes, automatically recompiling whenever it is updated (note that it's not 122 | smart enough to understand when 123 | 'included' 124 | files have been changed, though). 125 |
  • 126 |
127 |

128 | Because Twee2 source files are just plain text files, they're well-suited to use with 129 | source control systems: you could, for example, use 130 | Github 131 | to share your code or to collaborate with other authors, even working on the same file. 132 |

133 |

Understanding story formats

134 |

135 | Twee2 comes with built-in support for all of the same story formats that Twine 2 does: 136 |

137 |
    138 |
  • 139 | Harlowe 140 | - the default format, which includes a simplified scripting format 141 |
  • 142 |
  • 143 | Snowman 144 | - a minimal format that uses Underscore.js templates and jQuery to provide programmers with a powerful toolset 145 |
  • 146 |
  • 147 | SugarCube 148 | - a TiddlyWiki-powered format that comes with support for multiple save 'slots' and a Twine 1/Twee 'macro'-like scripting syntax 149 |
  • 150 |
  • 151 | Paperthin - used when you select "View Proofing Copy" in Twine 2, this minimal skeleton isn't really an output format as it is a proofing tool 152 |
  • 153 |
154 |

155 | If you've having difficulty choosing between them, there's 156 | a summary of the differences 157 | in the Twine 2 documentation. 158 |

159 |

160 | To specify which format to use during compilation, use the optional 161 | --format 162 | parameter. E.g. you might type 163 | twee2 build input.tw2 output.html --format=Snowman. 164 | You can get a list of the formats that Twee2 natively understands by running 165 | twee2 formats. 166 |

167 |

168 | It's possible to use any story format, e.g. if you've downloaded or written your own, by specifying the path 169 | to the story format's directory (the one containing the format.js file) in your 170 | --format 171 | parameter. For example, you might run 172 | twee2 build input.tw2 output.html --format=./MyFormat. 173 |

174 |

175 | Rather than setting the format on the command-line, it's possible to specify it within your source code 176 | itself, using 177 | build configuration 178 | options. 179 |

180 |

Twee2 syntax

181 |

182 | Twee2 uses a syntax that's heavily inspired-by but not 100% compatible with Twee: 183 |

184 |

Passages

185 |

186 | Each block of text in Twee2 exists in a 187 | passage. 188 | Each passage begins with a title, which is prefixed by two colons: 189 |

190 |

191 | ::My Passage Name 192 |

193 |

194 | Passage titles may only contain letters, numbers, basic punctuation, and spaces. Some authors prefer to avoid 195 | spaces in their passage titles. Passage titles are not case-sensitive: you don't have to use the same case when 196 | referring to a passage every time. Passages titles 197 | should 198 | be unique within a story: if they're not, only the last passage with a given title will be included in the story. 199 |

200 |

201 | Passage titles may optionally be suffixed by one or both of: 202 |

203 |
    204 |
  • 205 | Any number of 206 | tags, 207 | separated by spaced, inside a pair of square brackets. E.g: 208 |
    209 | ::My Passage Name [tagone tagtwo] 210 |
    211 | Tags can be used by code in your story (e.g. to send the player to a randomly-selected passage from a subset). 212 | Some tags have special meanings, 213 | as described below. 214 |
  • 215 |
  • 216 | A pair of coordinates, separated by a comma and enclosed within angle brackets. E.g.: 217 |
    218 | ::My Passage Name <123,456> 219 |
    220 | Coordinates have no meaning to Twee2, but they can be used to enhance compatability with Twine 2: Twine 2 uses 221 | these coordinates to decide where to show the passages in its WYSIWYG editor. 222 |
  • 223 |
224 |

225 | If you include both tags and coordinates after a passage, the tags must come before the coordinates, e.g.: 226 |

227 |

228 | ::My Passage Name [tagone tagtwo] <123,456> 229 |

230 |

231 | Some passages and tags 232 | have special meanings, 233 | as described below 234 |

235 |

Content

236 |

237 | Each passage may contain any amount of content, most of which will be stuff that the player sees. Many 238 | story formats understand 239 | Markdown 240 | formatting in passages, which makes it possible to easily add formatting to your passages. Links to different 241 | passages are supported in any of the following syntaxes (where 'Dungeon' is the name of the passage they'll be 242 | transported if they click the link, and 'click here', where present, is the text that they'll see to click on): 243 |

244 |
    245 |
  • 246 | [Dungeon] 247 |
  • 248 |
  • 249 | [click here->Dungeon] 250 |
  • 251 |
  • 252 | [Dungeon<-click here] 253 |
  • 254 |
  • 255 | [click here|Dungeon] 256 |
  • 257 |
258 |

Special passages

259 |

260 | Some passage names and tags have special meanings. These are: 261 |

262 |

::Start

263 |

264 | The 265 | ::Start 266 | passage will, by default, be used as the initial passage that your player sees when they start reading. 267 | You can override this using 268 | build configuration options, 269 | but it's probably easier just to follow this convention. 270 |

271 |

::StorySubtitle, ::StoryAuthor, ::StoryMenu, and ::StorySettings

272 |

273 | These names were used for special passages in Twee 1. They're not used by Twee2, but to maintain compatability 274 | with Twee 1 they're ignored and you should avoid using them unless you're writing a story that needs to be 275 | capable of being compiled by both Twee 1 and Twee2. 276 |

277 |

::StoryIncludes

278 |

279 | Any 280 | ::StoryIncludes 281 | passages (you can have as many as you like, but you probably should avoid having more than one in any file in 282 | order to avoid confusion) are treated as lists of secondary Twee2 files to 'include' into your story. This is 283 | described in more detail below, under 284 | splitting your code into multiple files. 285 |

286 |

[stylesheet]

287 |

288 | Any passages with the 'stylesheet' tag will not be included in your story, but their contents will be injected 289 | into the story's stylesheet. For example, you could write: 290 |

291 |

292 |

::MyCoolStylesheet [stylesheet]

body {
  background: #eee;
}

tw-passage tw-link {
  color: red;
}
293 |

294 |

295 | It's also possible to use 296 | SASS 297 | to enhance your stylesheet. Simply add the tag 'sass' or 'scss' to specify the dialect of SASS that you want 298 | to use, e.g.: 299 |

::MyCoolStylesheet [scss stylesheet]

body {
  background: #eee;
}

tw-passage {
  tw-link {
    color: red;
  }
}
300 |

301 |

[script]

302 |

303 | Any passages with the 'script' tag will not be included in your story, but their contents will be injected 304 | into the resulting web page as Javascript. For example, you could write: 305 |

306 |

307 |

::SomeAwesomeCode [script]

alert('This message will appear when the adventure starts!');
308 |

309 |

310 | It's also possible to use 311 | Coffeescript 312 | to make your Javascript development more-beautiful. Simply add the tag 'coffee' to your script block: 313 |

::SomeAwesomeCode [coffee script]

alert 'This message will appear when the adventure starts!'
314 |

315 |

[haml]

316 |

317 | Put the 'haml' tag into your regular passages in order to allow you to write 318 | HAML 319 | code into your passages. HAML is a sophisticated templating language for producing HTML output, and 320 | for some it might be preferable to writing plain old Markdown. This includes the ability to inject 321 | Javascript/CoffeeScript into particular passages. For example, you could write: 322 |

323 |

324 |

::NicksBar [haml]
%p
  Nick's Bar is exactly the kind of nightspot that helps you remember why you quit drinking.
  A depressed-looking barman pours another beer for an equally depressed-looking drunk, while
  over in the corner a street thug plays with a knife as he eyes you up. The floor is sticky
  and the air reeks of stale alcohol.
%p
  %strong What would you like to do?
%ul
  %li [Nick<-Talk to the bartender]
  %li [BarToilet<-Go to the bathroom]
  %li [BarSit<-Sit in a booth]

:coffeescript
  $ ->
    alert "This message will appear when you reach Nick's Bar!"
325 |

326 |

[twee2]

327 |

328 | Passages marked with the 'twee2' tag are not included in your story. However, any Ruby code in them 329 | will be executed when the passage is processed by the builder. This can be used to 330 | set build configuration options 331 | as described below. 332 |

333 |

Splitting your code into multiple files

334 |

335 | A major benefit of Twee2 over Twine 2 is that it's possible to break apart your story into multiple 336 | files, which can be used to structure your work, to facilitate teamworking with or without source 337 | control, and to make 'reusable' components such as stylesheets and scripts which can then be injected 338 | into later stories. There are two ways to do this: 339 |

340 |

Using ::StoryIncludes

341 |

342 | Insert a 343 | ::StoryIncludes 344 | passage into your file and list within it the names of the files to include. Each will be appended 345 | to the end of your story before compilation. For example, the following game skeleton includes the 346 | contents of four other files - the main file contains only the 347 | ::Start 348 | passage: 349 |

350 |

351 |

::Start
**Journey To The Centre Of The Earth**

by Dan Q

[Play]

::StoryIncludes
first_section.tw2
second_section.tw2
stylesheet.tw2
javascripts.tw2
352 |

353 |

Using ::@include directives

354 |

355 | You can put an 356 | ::@include 357 | directive anywhere in your code to insert the contents of another file at that point. 358 | ::@include 359 | respects your current indentation level (e.g. when using HAML), so it's safe to use at any indentation 360 | 'depth'. For example, the following passages each include a separate file that contains a description 361 | of the house in the distance: that description is shared between the two passages, and updating the 362 | file updates the description in both. 363 |

364 |

365 |

::Greenhouse [haml]
%p
  Light shines brightly through the tall glass walls of the ornate wrought-iron greenhouse, and
  the plants are verdant and lively. The [Garden<-door] stands open and a cool breeze blows in.
%p
  ::@include description-of-house.tw2

::Garden [haml]
%p
  The garden winds around decorative trees betwen the [greenhouse] and the [Porch<-mansion].
%p
  ::@include description-of-house.tw2
366 |

367 |

368 | Note that neither method of including code works recursively (i.e. you can't include a file that in 369 | turn includes another file), but this may be fixed in a future version. 370 |

371 |

Setting build configuration options

372 |

373 | Creating a passage with the 'twee2' tag can be used to run arbritrary Ruby code or to pass additional 374 | options to the Twee2 compiler. This expands the potential capability of Twee2 almost-limitlessly, but for now 375 | the only supported options are: 376 |

377 |
    378 |
  • 379 | 380 | Twee2::build_config.story_ifid = '[your IFID]' 381 | 382 | - used to set the 383 | IFID of your story, to facilitate catologuing. If you 384 | don't include this directive, the compiler will suggest a random one to you every time you run it and 385 | explain how to add it: if you're going to publish your story publicly, you should add the line it 386 | suggests before you do so. 387 |
  • 388 |
  • 389 | 390 | Twee2::build_config.story_format = '[a story format]' 391 | 392 | - an alternative way of specifying the story format used by your story, rather than using the --format 393 | switch to the compiler. The syntax of format names and means of specifying externally-stored formats is 394 | identical to 395 | using the --format switch. 396 |
  • 397 |
398 |

399 | For example: 400 |

401 |

402 |

::Configuration [twee2]
Twee2::build_config.story_ifid = '41AB7776-D198-40F5-BD54-0493D49DA58C'
Twee2::build_config.story_format = 'Snowman'
403 |

404 |

Decompiling Twine 2 stories

405 |

406 | It's possible to convert existing (compiled) Twee2/Twine 2 story files, in HTML format, back into 407 | Twee2 source files for further editing. This can be used to convert your Twine 2 projects into Twee2 408 | files or to easily examine the contents of somebody else's story. This feature does not work on Microsoft 409 | Windows. To use it, run: 410 |

411 |

412 | twee2 decompile input.html output.tw2 413 |

414 |

415 | The input HTML file can optionally be a full web URL. 416 |

417 |
418 |
419 | 444 |
445 | 446 | 447 | -------------------------------------------------------------------------------- /web/build/javascripts/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); --------------------------------------------------------------------------------