/
GameScriptContext.js
225 lines (193 loc) · 8.72 KB
/
GameScriptContext.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
*
*/
"use strict";
define(["Squishy", "./WumpusGame.Def", "../script/HostScriptContext"], function(squishy, wumpusGame, HostScriptContext) {
// ################################################################################################################################################################
// Define globals that will later exist in the guest context.
/**
* Define globals that are sent to and only executed in the guest script context.
* IMPORTANT: All these objects and code therein is run in the guest context.
*/
var guestGlobals = {
/**
* This code is called before before the context is secured.
* At this point, we can still load external resources and execute all kinds of potentially insecure functions inside the guest context.
* Do not run user code in this function.
*
* @param {Function} onInitDone Is called after initialization to signal the guest that this code has done its duty.
*/
initScriptContext: function(baseUrl, onInitDone) {
// configure requirejs
require.config({
baseUrl : baseUrl,
paths : {
Util: "js/util",
Squishy: "../SimplePlatformer/lib/squishy/squishy"
},
shim: {
}
});
// import game-related stuff
require([baseUrl + "js/core/WumpusGame.Def.js", "Squishy"], function() {
// Done loading. Signal that we are ready.
onInitDone("wumpusGame", "squishy"); // white-list these globals (will not be removed)
});
},
/**
* This code is called after the global context has been secured, so we can go ahead and add things that we like to the guest context.
* Do not run user code in this function.
*/
onInitializationFinished: function(onInitDone) {
// expose everything from the wumpusGame and squishy namespaces
exposeGlobals(wumpusGame);
exposeGlobals(squishy);
// create event handlers
var eventIds = wumpusGame.PlayerEvent.getValues();
self.eventHandlers = createGlobalEvents(eventIds, getEventHandlerNameById);
// signal guest context that we are ready
onInitDone();
},
/**
* These are extra message handlers to implement a complete game protocol on the guest side.
* You can run user code in this function.
* Each commandHandler has one parameter: args (the arguments given in the postCommand call)
* It also has an optional parameter: postPrivilegedCommand, which is a function to send privileged commands from the guest back to the host.
* This parameter will only be available if the commandHandler is registered as an object with two properties: handler (the actual handler function) and isPrivileged (must be set to true)
*/
commandHandlers: {
/**
* This function is called to signal a new set of player events.
*/
triggerPlayerEvents: function(events) {
events.forEach(function (event) {
var eventId = event.eventId;
var args = event.args;
eventHandlers[eventId](args);
});
}
},
/**
* These variables are just exposed to the guest's global context for use in user script code.
* These are mostly aimed at sending messages (or commands or actions) to avoid users having to send those themselves.
*
* TODO: Create a reliable structure to check when, how and why user actions fail, and inform the guest and/or the user (depending on context).
*/
globals: {
/**
* Move forward one tile
*/
moveForward: function() {
postAction(wumpusGame.PlayerAction.Forward);
},
/**
* Move backward one tile
*/
moveBackward: function() {
postAction(wumpusGame.PlayerAction.Backward);
},
/**
* Turn clockwise by 90 degrees
*/
turnClockwise: function() {
postAction(wumpusGame.PlayerAction.TurnClockwise);
},
/**
* Turn counter clockwise by 90 degrees
*/
turnCounterClockwise: function() {
postAction(wumpusGame.PlayerAction.TurnCounterClockwise);
},
/**
* Escape through an entrance.
*/
exit: function() {
postAction(wumpusGame.PlayerAction.Exit);
},
/**
* Returns the name of the event of the given id.
*/
getEventHandlerNameById: function(eventId) {
var eventName = wumpusGame.PlayerEvent.toString(eventId);
return "on" + eventName;
}
}
};
// ################################################################################################################################################################
// GameScriptContext class (inherits from HostScriptContext)
/**
* IMPORTANT: All this code is run in the host context.
*/
wumpusGame.GameScriptContext = squishy.extendClass(HostScriptContext,
/**
* Creates a new GameScriptContext.
* @constructor
*/
function (game, scriptConfig) {
this._super(scriptConfig); // call base constructor
// assign game
squishy.assert(game, "game was not defined");
this.game = game;
// register game event listeners:
// register game restart event listener
this.game.events.restart.addListener(function() {
this.resetContext();
}.bind(this));
// register player event listener
this.game.events.playerEvent.addListener(function(player, events) {
// remember all events
this.eventLog.push(events);
// send to remote side
this.postCommand("triggerPlayerEvents", events);
}.bind(this));
// register guest response event listeners:
this.setGuestResponseHandler("action", this.onAction.bind(this), true);
// tell the worker to send these variables to the GuestScriptContext during initialization and add them to the guest's global object.
this.setGuestGlobals(guestGlobals);
this.resetContext();
},
// define methods
{
/**
* Called when game restarts.
*/
resetContext: function() {
// reset eventlog
this.eventLog = [];
this.eventLogIndex = 0;
this.scriptRan = false;
},
/**
* Called after initialization finished, and guest context is ready.
* Note that this is called in the host context.
*/
onGuestReady: function() {
},
/**
* Called when the user script sends an action.
* Make sure that this is interpreted securely. The user might cheat.
*/
onAction: function(action) {
// action to be performed by agent
var player = this.game.player;
player.performActionDelayed(action);
},
/**
* Restarts the game and runs the given user script
*/
startUserScript: function(code, name) {
// restart game and clean slate the entire guest context to avoid compatability issues between two script runs...
this.game.restart();
// run the user script (which will (re-)register event handlers)
this.runUserCode(code, name);
// play log of events that happened during initialization
for (; this.eventLogIndex < this.eventLog.length; ++this.eventLogIndex) {
// send to remote side
var events = this.eventLog[this.eventLogIndex];
this.postCommand("triggerPlayerEvents", events);
}
}
}
);
return wumpusGame.GameScriptContext;
});