demiurge 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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 }