demiurge 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/.yardopts +5 -0
- data/AUTHORS.txt +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONCEPTS.md +271 -0
- data/Gemfile +4 -0
- data/HACKING.md +34 -0
- data/LICENSE.txt +21 -0
- data/README.md +181 -0
- data/RELOADING.md +94 -0
- data/Rakefile +10 -0
- data/SECURITY.md +103 -0
- data/WORLD_FILES.md +134 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/demiurge.gemspec +31 -0
- data/exe/demirun +16 -0
- data/lib/demiurge/action_item.rb +643 -0
- data/lib/demiurge/agent.rb +338 -0
- data/lib/demiurge/container.rb +194 -0
- data/lib/demiurge/dsl.rb +583 -0
- data/lib/demiurge/exception.rb +170 -0
- data/lib/demiurge/inert_state_item.rb +21 -0
- data/lib/demiurge/intention.rb +164 -0
- data/lib/demiurge/location.rb +85 -0
- data/lib/demiurge/notification_names.rb +93 -0
- data/lib/demiurge/tmx.rb +439 -0
- data/lib/demiurge/util.rb +67 -0
- data/lib/demiurge/version.rb +4 -0
- data/lib/demiurge/zone.rb +108 -0
- data/lib/demiurge.rb +812 -0
- metadata +165 -0
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
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
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 }
|