demiurge 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/RELOADING.md ADDED
@@ -0,0 +1,94 @@
1
+ # Reloading, Debugging and Demiurge Engines
2
+
3
+ Demiurge tries to make it easy to reload World Files in
4
+ development. What's so complicated about that?
5
+
6
+ Where possible, Demiurge tries to just keep the same zones, locations,
7
+ agents and so on with the same action names. If you change the code
8
+ for something that already exists, great! Demiurge can easily put that
9
+ together and give you the new code.
10
+
11
+ Fabulous. All done, right?
12
+
13
+ Nope. What if you rename a zone? You probably don't *want* Demiurge to
14
+ try to guess that based on the line in the file, or the new name. If
15
+ it gets it right 60% of the time (which is optimistic)... Well, what
16
+ about the other 40%? Also, you won't be in the habit of noticing and
17
+ fixing it, which will occasionally be *really* bad.
18
+
19
+ So: expect this to be okay for development. Do *not* expect it to work
20
+ perfectly, nor for you to be able to test a bunch of development
21
+ changes with incremental reloads and then have it work perfectly in
22
+ production later.
23
+
24
+ State is hard. Reloading is hard. Demiurge has some ways to help a
25
+ little. But this is all still hard.
26
+
27
+ ## Resetting
28
+
29
+ ## Simple Best Practices
30
+
31
+ When Demiurge loads your World Files, it has to execute them as Ruby
32
+ code. That means if you do something as a side effect (print to
33
+ console, write a file) it gets done every time. So try not to do that.
34
+
35
+ ## What Makes This Easier?
36
+
37
+ Players love big persistent games like Minecraft where everything they
38
+ do persists forever and they can make huge changes to the game. And
39
+ yet there are very few games like that. Why?
40
+
41
+ Partly because it's really, really hard to *test* changes to a world
42
+ like that. Wouldn't it be cool if you could add new lava-or-water-type
43
+ blocks to Minecraft and change how they worked? Maybe you could make
44
+ some kind of slime-or-pudding block that poured and flowed like that?
45
+ And then make pudding sculptures, like those wonderful towers or
46
+ fortresses you make with water or lava pouring over them in Minecraft
47
+ Creative Mode? Now, think about how you'd *test* those changes in a
48
+ big persistent Minecraft-type world where everybody was building stuff
49
+ all the time.
50
+
51
+ (For the same reason, a Minecraft MMO would be a *gigantic* pain to
52
+ create. Just sayin'.)
53
+
54
+ If you were making changes to how the pudding blocks worked, you'd
55
+ have players *howling* every time the flow changed and their
56
+ fortresses looked different. You can't just put that out there and
57
+ then mess with it -- the "mess with it" part is really hard in a big
58
+ persistent world. Players get really attached to every small change.
59
+
60
+ So what do you do? Well, one thing is to wipe the slate clean,
61
+ regularly. Minecraft doesn't guarantee that you know exactly where
62
+ every skeleton or creeper is, and mostly you don't expect to. Most
63
+ games are even more like that -- World of Warcraft knows where
64
+ monsters *spawn* and it knows their *routes*, but you don't expect to
65
+ come back an hour later and see anything you did persist. Dropped
66
+ items disappear, killed monsters come back.
67
+
68
+ By having a lot of non-persisted state (information the game is
69
+ allowed to wipe clean), you leave room for making changes. If an admin
70
+ messes around with exactly how monsters spawn (how many? what timing?
71
+ do they roam around a little? where?) that doesn't feel like a big
72
+ deal -- you're used to that information getting reset constantly, and
73
+ little changes to it feel like window dressing. You forgot exactly
74
+ which monsters you killed thirty seconds after you killed them because
75
+ you don't *expect* that to be useful to remember. The spawn points
76
+ might be important - especially if you're used to sneaking past them
77
+ or quietly taking a route that keeps you out of the way of the big
78
+ nasties. But even changing those spawn points feels "fair" - it's
79
+ different from losing track of, say, how much gold your character
80
+ currently has, or exactly which blocks you put where in Minecraft to
81
+ make a shelter.
82
+
83
+ In general, you should make it clear when there's information you
84
+ won't be persisting. If you need to wipe out dropped weapons on the
85
+ floor (you probably do) then start that decay process *early* - you
86
+ want folks to know it's possible so that it's not a horrible surprise
87
+ when it happens.
88
+
89
+ And you want to persist as little information as you can get away
90
+ with. Any part of the game world that you (the admin) are allowed to
91
+ play with or reset is information you don't need to keep intact as
92
+ precious player data.
93
+
94
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/SECURITY.md ADDED
@@ -0,0 +1,103 @@
1
+ # Security and User-Supplied Code
2
+
3
+ Demiurge is designed for an environment with very high-privilege code
4
+ (e.g. Demiurge itself, framework code, setup code) and somewhat
5
+ lower-privilege code (e.g. world files, certain player actions,
6
+ display code like particles.) It is *not* designed to let you just
7
+ "drop in" untrusted code and expect everything will be fine, basically
8
+ ever.
9
+
10
+ ## Sandboxing
11
+
12
+ In general, "sandboxing" code to prevent it from doing things you
13
+ don't want is very hard. Ruby does nothing to make this easier - only
14
+ a few languages are easy to "sandbox", with JavaScript being by far
15
+ the most advanced in that specific respect -- it's designed to run in
16
+ the browser environment in a very specifically sandboxed way.
17
+
18
+ There are a few Ruby sandbox implementations (the current leader seems
19
+ to be [JRuby Sandbox]("https://github.com/jwo/jruby-sandbox")), but
20
+ in general they're hard to get right. Proving that a particular Ruby
21
+ snippet can't access a particular class (say ObjectSpace, which gives
22
+ full access to 100% of everything, or any flavor of "eval," or
23
+ "binding," or...) is very, very hard.
24
+
25
+ So: the current Demiurge codebase makes a much simpler assumption:
26
+ there are two overall flavors of code and neither is safe.
27
+
28
+ The first flavor is administrative code. Anything that runs with full
29
+ privilege (actions marked as full-privilege, anything in a framework
30
+ file or other non-World dot-rb file) must be examined very
31
+ carefully. These are full-danger code that can do absolutely anything,
32
+ and you must trust such code as much as the code you wrote for
33
+ yourself. They can do anything you can do, and running them "just for
34
+ this one person" without fully examining them is a terrible, terrible
35
+ idea.
36
+
37
+ ## World Code
38
+
39
+ The second flavor is world code. Non-administrative zones, actions for
40
+ locations and agents and other similar code is world code. It runs
41
+ with more restrictions. It doesn't have methods to directly call most
42
+ dangerous operations like reloading your world from a state dump or
43
+ reloading a zone. You still have to examine it - a creative Ruby coder
44
+ can potentially get at just about anything they want to. But the basic
45
+ operations it provides are less dangerous, and a very simple chunk of
46
+ world code will normally not be able to do anything *too* awful.
47
+
48
+ However, the
49
+ [Halting Problem](https://en.wikipedia.org/wiki/Halting_problem)
50
+ guarantees that we can't make many guarantees about what code does,
51
+ and we certainly can't be sure it returns in a crisp, predictable
52
+ manner in general. So: assume that even world code, even very simple
53
+ world code, can cause you significant problems.
54
+
55
+ ## Special Cases
56
+
57
+ There are very restricted cases where you can be sure that everything
58
+ is fine. As an intentionally-silly example, if you let the player
59
+ supply a six-digit hexadecimal color value and then apply it to be the
60
+ color of something using CSS... That's pretty darn securable. If you
61
+ want to make that safe, you totally can.
62
+
63
+ And as an intermediate-difficulty thing, it's not hard to come up with
64
+ a definition language for things like particle effects that only have
65
+ a few operations, limited conditionals and no loops, which are
66
+ essentially safe. At most a bad world-builder might be able to slow
67
+ down people's browsers, which isn't a huge deal.
68
+
69
+ But in general, if a language is [Turing Complete](https://en.wikipedia.org/wiki/Turing_completeness) then there's a way
70
+ to make it misbehave in a way you can't automatically detect. Which
71
+ means any language interesting enough to build a world in is also
72
+ interesting enough to cause you lots of problems with securing it
73
+ automatically.
74
+
75
+ It's still not a bad idea to build special cases. Those are where
76
+ you'll be able to let *players* supply content without carefully
77
+ vetting it. If you need world-builders to write zones in Ruby then you
78
+ need to have somebody review the code manually - there's not going to
79
+ be an enforcement system that's good enough to catch all the
80
+ problems. But if you want players to color their own gear or even make
81
+ up their own custom particle effects, that's probably not a problem.
82
+
83
+ It's surprising how much you can do with a special-purpose language or
84
+ format for just a few specific things. CSS in your browser is an
85
+ example of how far you can take this idea, though these days CSS is
86
+ Turing Complete too...
87
+
88
+ ## How Do We Do It Right?
89
+
90
+ There is an eventual solution that could help this a fair bit, at a
91
+ cost of performance and complexity. By running a separate Ruby process
92
+ with limited privilege, it could become possible to halt a runaway
93
+ chunk of world code and watch its operations more carefully - it's
94
+ always easier to monitor a chunk of Ruby code from *outside* that Ruby
95
+ process.
96
+
97
+ That gets around problems with the various sandbox libraries -- how do
98
+ you avoid them using 100% CPU and never returning? How do you get
99
+ around them allocating unlimited memory? These aren't problems you can
100
+ get around with a Ruby-space sandbox library, not really. You need to
101
+ run the code evaluation in a separate process so that you can limit
102
+ those things without putting your limiting code in the same Ruby
103
+ interpreter, where "bad actor" code can get at it.
data/WORLD_FILES.md ADDED
@@ -0,0 +1,134 @@
1
+ # Demiurge World Files
2
+
3
+ Demiurge includes a very convenient Ruby DSL (Domain-Specific
4
+ Language) for declaring your zones, locations, agents and so on. These
5
+ are referred to as World Files, and are by far the easiest and most
6
+ common way to create a simulation using Demiurge.
7
+
8
+ Unfortunately, the Builder classes in the API documentation aren't a
9
+ great way to document the World File syntax and how to use it. There
10
+ are some good examples, but many of them are either too complicated,
11
+ or intentionally weird. For instance, the test/ directory is full of
12
+ World File examples that are designed to test weird corner cases or
13
+ generate errors or both.
14
+
15
+ This is a quick guide to using World Files. It's not full and
16
+ comprehensive. But it may provide a kick-start, after which the
17
+ Builder classes won't seem quite so bizarre and inscrutable.
18
+
19
+ Normally World Files are stored in a directory in your game. I like
20
+ calling it "world". If you used multiple Demiurge engines for some
21
+ reason, you'd probably want to store their two sets of World Files in
22
+ two different directories. But for a single Engine, you'd normally
23
+ want a single directory of World Files.
24
+
25
+ ## Getting Started
26
+
27
+ Your game will need to load its World Files somehow. Here's a good
28
+ simple example of how to do that:
29
+
30
+ Dir["**/world/extensions/**/*.rb"].each do |ruby_ext|
31
+ require_relative ruby_ext
32
+ end
33
+ @engine = Demiurge.engine_from_dsl_files *Dir["world/*.rb"]
34
+
35
+ This example doesn't try to do any kind of sorting to load the files
36
+ in a particular order. In other words, you'll need to do something
37
+ fancier if your game gets big.
38
+
39
+ It also assumes that all your World Files are in the top-level
40
+ directory, which won't stay true for long either. So: you can start
41
+ with this example, but you'll need to change it later.
42
+
43
+ ## Your First Zone
44
+
45
+ Here's an example file containing a very simple zone for use with
46
+ Demiurge-CreateJS:
47
+
48
+ zone "trackless island", "type" => "TmxZone" do
49
+ tmx_location "start location" do
50
+ manasource_tile_layout "tmx/trackless_island_main.tmx"
51
+ description "A Mysterious Island"
52
+ end
53
+ end
54
+
55
+ If you're using Demiurge-CreateJS (aka DCJS) then you'll probably want
56
+ to use 2D tile-based locations, also called TmxLocations. Those are
57
+ the ones you can generate using the Tiled map editor. See
58
+ Demiurge-CreateJS for more details.
59
+
60
+ If you're using Demiurge on your own projects without DCJS, you can do
61
+ that too. Here's an example administrative zone containing a player
62
+ definition:
63
+
64
+ # Player information can be stored here. In general, if you want
65
+ # information to stick around it needs a StateItem. An "inert"
66
+ # StateItem just means it doesn't change on its own or take any
67
+ # actions.
68
+ inert "players"
69
+
70
+ zone "adminzone" do
71
+ agent "player template" do
72
+ # No position or location, so it's instantiable.
73
+
74
+ # Special action that gets performed on character creation
75
+ define_action("create", "engine_code" => true) do
76
+ if ["bob", "angelbob", "noah"].include?(item.name)
77
+ item.state["admin"] = true
78
+ end
79
+ end
80
+
81
+ define_action("statedump", "tags" => ["admin", "player_action"]) do
82
+ dump_state
83
+ end
84
+ end
85
+ end
86
+
87
+ You can create interesting object interactions with actions. You can
88
+ see a few actions above, but they're just the tip of the iceberg.
89
+ Here's a fun example where an agent can kick some stones down a cliff
90
+ and they'll start a mini-avalanche. The bigger the initial kick, the
91
+ bigger (by far) the total number of stones falling down:
92
+
93
+ zone "echoing cliffs" do
94
+ location "cliffside" do
95
+
96
+ agent "stone kicker" do
97
+ define_action "kick stones" do |how_many|
98
+ notification type: "echoing noise", "stone size": how_many, location: item.location_name
99
+ how_many -= 1
100
+ if how_many > 0
101
+ action "kick stones", how_many
102
+ action "kick stones", how_many
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ Each action block (the do/end part) is just Ruby code, so you can do
110
+ whatever Ruby can do there. The hard part is just figuring out what
111
+ information you can work with, and what you're allowed to do with
112
+ it. The methods above called "notification", "action" and "dump_state"
113
+ above are all standard and can be found in the BlockRunner classes -
114
+ that's AgentBlockRunner, ActionItemBlockRunner and EngineBlockRunner,
115
+ depending on how you run it. Agents get the AgentBlockRunner,
116
+ ActionItems get the ActionItemBlockRunner, and you only get the
117
+ EngineBlockRunner if you specifically pass the ":engine_code" option
118
+ to your action.
119
+
120
+ ## Security
121
+
122
+ There's a SECURITY guide in its own file. But the short version is:
123
+ keep in mind that World Files are *not* secure. You should read
124
+ through any World File that somebody else gives you completely before
125
+ you add it to your game. A bad World File can steal your in-game
126
+ passwords and code. It can even attack other programs running on the
127
+ same server as your game (which might be your home computer, where you
128
+ do your banking.) It can send somebody else your data, and it can
129
+ download other code from the Internet if it's connected to the
130
+ Internet.
131
+
132
+ Do *not* accept unknown World Files from other people. It's like
133
+ running a downloaded .EXE on a Windows machine - it can do whatever
134
+ your computer can, and you really shouldn't let it.
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "demiurge"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/demiurge.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'demiurge/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "demiurge"
8
+ spec.version = Demiurge::VERSION
9
+ spec.authors = ["Noah Gibbs"]
10
+ spec.email = ["the.codefolio.guy@gmail.com"]
11
+
12
+ spec.summary = %q{A creator and manager for game rules and state.}
13
+ spec.description = %q{A creator and manager for game rules and state, separate from displaying and controlling the game. The idea is that a primarily-simulation game may be written using Ruby rules and a connection to the Demiurge process, while control and display are handled separately. This approach is primarily useful for 'simulation' games rather than fast-reflex 'twitch' games.}
14
+ spec.homepage = "https://github.com/noahgibbs/demiurge"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_runtime_dependency "multi_json", "~>1.12"
25
+ spec.add_runtime_dependency "tmx", "~>0.1.5"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.14"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ spec.add_development_dependency "yard", "~> 0.9"
31
+ end
data/exe/demirun ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby -w -I./lib
2
+
3
+ require "demiurge"
4
+ require "demiurge/dsl"
5
+ require "demiurge/tmx"
6
+
7
+ require "multi_json"
8
+
9
+ if ARGV.size < 1
10
+ raise "Please give at least one Demiurge DSL file to parse!"
11
+ end
12
+
13
+ engine = Demiurge.engine_from_dsl_files(*ARGV)
14
+ engine.finished_init
15
+
16
+ loop { engine.advance_one_tick }