8fe220f63a39ac710d93b8402fe35192e682dfe9
[wgj58.git] / wgj58.js
1 var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: function() { this.state.add('GamePlay', GamePlay, true); } });
2 var logic;
3 var cursors;
4
5 const PLAYER_SPEED = 200;
6 const WAIT_MENUSTEP = 100;
7 const WAIT_TALK = 250;
8
9 const MENUITEM_TALK = 0;
10 const MENUITEM_LEAVE = 1;
11 const MENUITEM_TAKE = 2;
12
13 const GUI_MENUITEM_DISTANCE = 20;
14
15 window.addEventListener("keydown", function(e) {
16 // Prevent default browser action for arrows and spacebar
17 if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
18 e.preventDefault();
19 }
20 }, false);
21
22 function hasTimePassed(time, delay) {
23 return (game.time.now - time) > delay;
24 }
25
26 class Dialogue {
27 // dialogue is array of {actor, text} records.
28 constructor(dialogue) {
29 this.dialogue = dialogue;
30 this.state = 0;
31 }
32
33 actual() {
34 return this.dialogue[this.state];
35 }
36
37 advance() {
38 this.state++;
39 }
40 }
41
42 class Door extends Phaser.TileSprite {
43 constructor(x, y, name, rotation, vector, longpanel) {
44 super(game, x, y, 64, 64, 'objects');
45 this.name = name;
46 if (!longpanel) {
47 this.anchor = new Phaser.Point(0.5, 0.5);
48 this.tilePosition = new Phaser.Point(-64, -64);
49 this.openvector = Phaser.Point.multiply(vector, new Phaser.Point(56, 56));
50 }
51 else {
52 this.width *= 2;
53 this.anchor = new Phaser.Point(0.75, 0.5);
54 this.tilePosition = new Phaser.Point(0, -64);
55 this.openvector = Phaser.Point.multiply(vector, new Phaser.Point(116, 116));
56 }
57 this.rotation = rotation * (Math.PI / 180);
58 console.log("Calculated vector:", this.openvector);
59 this.closetween = game.add.tween(this).to({ x: this.position.x, y: this.position.y }, 1000, Phaser.Easing.Sinusoidal.InOut, false, 0, 0, false);
60 this.openposition = Phaser.Point.add(this.position, this.openvector);
61 this.opentween = game.add.tween(this).to({ x: this.openposition.x, y: this.openposition.y }, 1000, Phaser.Easing.Sinusoidal.InOut, false, 0, 0, false);
62 this.isOpen = false;
63 }
64
65 open() {
66 if (!this.isOpen) {
67 this.opentween.start();
68 this.isOpen = true;
69 }
70 }
71
72 close() {
73 if (this.isOpen) {
74 this.closetween.start();
75 this.isOpen = false;
76 }
77 }
78 }
79
80 class Player extends Phaser.Sprite {
81 constructor(x, y) {
82 super(game, x, y, 'player');
83 this.y -= this.height;
84 this.shortname = "John";
85 this.fullname = "John Evals";
86 this.offerInteraction(null);
87 this.unfreeze();
88 }
89
90 enablePhysics() {
91 game.physics.arcade.enable(this);
92 this.body.bounce.y = 0.2;
93 this.body.bounce.x = 0.2;
94 this.body.collideWorldBounds = true;
95 }
96
97 control() {
98 if ((this.alive) && (!this.freezed))
99 {
100 if (cursors.left.isDown)
101 {
102 this.body.velocity.x = -PLAYER_SPEED;
103 }
104 else if (cursors.right.isDown)
105 {
106 this.body.velocity.x = PLAYER_SPEED;
107 }
108 if (cursors.up.isDown)
109 {
110 this.body.velocity.y = -PLAYER_SPEED;
111 }
112 else if (cursors.down.isDown)
113 {
114 this.body.velocity.y = PLAYER_SPEED;
115 }
116 }
117 }
118
119 update() {
120 this.body.velocity.x = this.body.velocity.y = 0;
121 this.control();
122 }
123
124 freeze() {
125 this.freezed = true;
126 }
127
128 unfreeze() {
129 this.freezed = false;
130 }
131
132 offerInteraction(npc) {
133 this.interactablenpc = npc;
134 }
135
136 takeMe(npc) {
137 this.loadTexture(npc.key);
138 npc.kill();
139 }
140 }
141
142 class GameNPC extends Phaser.Sprite {
143 constructor(x, y, key, shortname, fullname, interaction_distance) {
144 super(game, x, y, key);
145 this.y -= this.height;
146 this.shortname = shortname;
147 this.fullname = fullname;
148 this.interaction_distance = interaction_distance;
149 this.interactable = false;
150 this.talkcount = 0;
151 }
152
153 kill() {
154 super.kill();
155 this.exists = false;
156 }
157
158 update() {
159 super.update();
160 if ((!this.interactable) && (game.physics.arcade.distanceBetween(this, logic.player) < this.interaction_distance)) {
161 logic.gameinterface.dropNotice("(ENTER) Interact with " + this.shortname + "!");
162 logic.player.offerInteraction(this);
163 this.interactable = true;
164 }
165 else if ((this.interactable) && (game.physics.arcade.distanceBetween(this, logic.player) > this.interaction_distance)) {
166 logic.gameinterface.clearNotice();
167 logic.player.offerInteraction(null);
168 this.interactable = false;
169 }
170 }
171
172 actionTalk() {
173 this.talkcount++;
174 }
175
176 actionLeave() {
177 }
178
179 actionTake() {
180 logic.player.offerInteraction(null);
181 logic.player.takeMe(this);
182 return true;
183 }
184
185 endTalk() {
186 return true;
187 }
188 }
189
190 class NPC_Clara extends GameNPC {
191 actionTalk() {
192 switch (this.talkcount) {
193 case 0:
194 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "What a morning..." },
195 { actor: logic.player, text: "Hi Clara! What happened?" },
196 { actor: this, text: "No one cares to tell. I just know that I have to log on people manually, as the access control system doesn't work properly." },
197 { actor: logic.player, text: "Hmm... I'll look into it. Maybe it's just that everyone is fired." },
198 { actor: this, text: "Haha! Wouldn't joke about this, though, due to the recent layoffs." },
199 { actor: logic.player, text: "Well, maybe if we dare to joke about it, it won't happen to us..." },
200 { actor: this, text: "Wish it worked like that... Is it some superstition like the belief that having an umbrella with you prevents rain?" },
201 { actor: logic.player, text: "Nah, that actually works; it's not a superstition, but Murphy's Law!" },
202 { actor: this, text: "If you say so..." } ] ));
203 logic.openDoor("cutedoor");
204 break;
205 case 1:
206 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "John, have you ever thought about losing your employee card?" },
207 { actor: logic.player, text: "Yeah, you'd just get a new one." },
208 { actor: this, text: "You don't understand me, John!" },
209 { actor: this, text: "..." },
210 { actor: this, text: "I mean... Losing it for real..." },
211 { actor: this, text: "Doesn't it feel like it's the culmination of your being?" },
212 { actor: this, text: "If I had no card, would I still exist?" },
213 { actor: logic.player, text: "..." },
214 { actor: logic.player, text: "You sound very philosophical today." } ] ));
215 logic.closeDoor("cutedoor");
216 break;
217 case 2:
218 case 3:
219 case 4:
220 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Would you like to hear a random fun fact?" },
221 { actor: logic.player, text: "Of course!" },
222 { actor: this, text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s," },
223 { actor: this, text: "when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap" },
224 { actor: this, text: "into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem" },
225 { actor: this, text: "Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." },
226 { actor: logic.player, text: "That's very interesting, I didn't know that!" },
227 { actor: logic.player, text: "Thanks for sharing, Clara!" } ] ));
228 break;
229 case 5:
230 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Would you like to hear a random fun fact?" },
231 { actor: logic.player, text: "Of course!..." },
232 { actor: logic.player, text: "But first..." },
233 { actor: this, text: "What is it?" },
234 { actor: logic.player, text: "..." },
235 { actor: logic.player, text: "Never mind, go on with what you wanted to say." },
236 { actor: this, text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry..." },
237 { actor: logic.player, text: "I knew that already..." },
238 { actor: logic.player, text: "But..." },
239 { actor: logic.player, text: "Would you go out on a date with me?" },
240 { actor: this, text: "..." },
241 { actor: this, text: "No." } ] ));
242 break;
243 default:
244 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "..." } ] ));
245 }
246 super.actionTalk();
247 }
248
249 actionLeave() {
250 logic.gameinterface.dropNotice(this.shortname + ": Have a great day, John!");
251 }
252
253 endTalk() {
254 return (this.talkcount < 6);
255 }
256 }
257
258
259 class GameInterface extends Phaser.Group {
260 constructor(game, parent) {
261 super(game, parent, 'GUI', false, false, 0);
262 this.back_notice = new Phaser.Graphics(game, 0, game.height - 50);
263 this.back_notice.beginFill(0x000000);
264 this.back_notice.drawRect(0, 0, game.width, 30);
265 this.back_notice.endFill();
266 this.add(this.back_notice);
267 this.text_notice = new Phaser.Text(game, 0, this.back_notice.y, null, { align: 'left', fill: 'white', font: 'Ubuntu Mono', fontSize: 18, fontWeight: 'bold' });
268 this.text_notice.anchor.setTo(-0.5, -0.2);
269 this.add(this.text_notice);
270 this.clearNotice();
271
272 this.back_menu = new Phaser.Graphics(game, 150, (game.height / 2) + 40);
273 this.back_menu.beginFill(0x000000);
274 this.back_menu.drawRect(0, 0, 155, 105);
275 this.back_menu.endFill();
276 this.back_menu.lineStyle(3, Phaser.Color.YELLOW, 1);
277 this.back_menu.moveTo(10, 35);
278 this.back_menu.lineTo(this.back_menu.width - 10, 35);
279 this.add(this.back_menu);
280 var style = { align: 'left', fill: 'yellow', font: 'Ubuntu Mono', fontSize: 22, fontWeight: 'bold' };
281 this.text_menutitle = new Phaser.Text(game, this.back_menu.x, this.back_menu.y, null, style);
282 this.text_menutitle.anchor.setTo(-0.2, -0.2);
283 style = { align: 'left', fill: 'white', font: 'Ubuntu Mono', fontSize: 18, fontWeight: 'bold' };
284 this.text_menuitem_Talk = new Phaser.Text(game, this.back_menu.x + 35, this.back_menu.y + 40, "Talk", style);
285 this.text_menuitem_Leave = new Phaser.Text(game, this.back_menu.x + 35, this.text_menuitem_Talk.y + GUI_MENUITEM_DISTANCE, "Leave", style);
286 this.text_menuitem_Take = new Phaser.Text(game, this.back_menu.x + 35, this.text_menuitem_Leave.y + GUI_MENUITEM_DISTANCE, "Take ID", style);
287 style.fill = 'yellow';
288 this.text_menucursor = new Phaser.Text(game, this.back_menu.x + 15, this.text_menuitem_Talk.y, "→", style);
289 this.add(this.text_menutitle);
290 this.add(this.text_menuitem_Talk);
291 this.add(this.text_menuitem_Leave);
292 this.add(this.text_menuitem_Take);
293 this.add(this.text_menucursor);
294 this.leaveMenu();
295 this.last_menustep = 0;
296
297 this.back_talk = new Phaser.Graphics(game, 100, game.height - 120);
298 this.back_talk.beginFill(0x000000);
299 this.back_talk.drawRect(0, 0, game.width - 200, 105);
300 this.back_talk.endFill();
301 this.back_talk.lineStyle(3, Phaser.Color.YELLOW, 1);
302 this.back_talk.moveTo(10, 35);
303 this.back_talk.lineTo(this.back_talk.width - 10, 35);
304 this.add(this.back_talk);
305 style = { align: 'left', fill: 'yellow', font: 'Ubuntu Mono', fontSize: 22, fontWeight: 'bold' };
306 this.text_talktitle = new Phaser.Text(game, this.back_talk.x, this.back_talk.y, null, style);
307 this.text_talktitle.anchor.setTo(-0.2, -0.2);
308 this.add(this.text_talktitle);
309 style = { align: 'left', fill: 'white', font: 'Ubuntu Mono', fontSize: 18, fontWeight: 'bold' };
310 this.text_talk = new Phaser.Text(game, this.back_talk.x + 35, this.back_talk.y + 40, null, style);
311 this.text_talk.wordWrap = true;
312 this.text_talk.wordWrapWidth = this.back_talk.width - 70;
313 this.text_talk.lineSpacing = -5;
314 this.add(this.text_talk);
315 this.leaveTalk();
316 this.last_talk = 0;
317 }
318
319 dropNotice(text) {
320 this.text_notice.text = text;
321 this.back_notice.visible = true;
322 this.text_notice.visible = true;
323 }
324
325 clearNotice() {
326 this.back_notice.visible = false;
327 this.text_notice.visible = false;
328 }
329
330 npcMenu(npc) {
331 if (!this.inMenu) {
332 this.inMenu = true;
333 this.clearNotice();
334 this.text_menutitle.text = npc.shortname;
335 this.back_menu.visible = true;
336 this.text_menutitle.visible = true;
337 this.text_menuitem_Talk.visible = true;
338 this.text_menuitem_Leave.visible = true;
339 this.text_menuitem_Take.visible = true;
340 this.currentmenuitem = MENUITEM_TALK;
341 this.actualizeCursorPosition();
342 this.text_menucursor.visible = true;
343 this.last_menustep = game.time.now;
344 }
345 }
346
347 leaveMenu() {
348 this.back_menu.visible = false;
349 this.text_menutitle.visible = false;
350 this.text_menuitem_Talk.visible = false;
351 this.text_menuitem_Leave.visible = false;
352 this.text_menuitem_Take.visible = false;
353 this.text_menucursor.visible = false;
354 this.inMenu = false;
355 }
356
357 talk(dialogue) {
358 this.inTalk = true;
359 this.dialogue = dialogue;
360 this.advanceTalk();
361 this.back_talk.visible = true;
362 this.text_talktitle.visible = true;
363 this.text_talk.visible = true;
364 }
365
366 leaveTalk() {
367 this.inTalk = false;
368 this.dialogue = null;
369 this.back_talk.visible = false;
370 this.text_talktitle.visible = false;
371 this.text_talk.visible = false;
372 }
373
374 advanceTalk() {
375 console.log(this.dialogue);
376 console.log(this.dialogue.actual());
377 var actualdialogue = this.dialogue.actual();
378 if (actualdialogue) {
379 this.text_talktitle.text = actualdialogue.actor.shortname;
380 this.text_talk.text = actualdialogue.text;
381 this.dialogue.advance();
382 }
383 else {
384 this.leaveTalk();
385 logic.endTalk();
386 }
387 this.last_talk = game.time.now;
388 }
389
390 actualizeCursorPosition() {
391 this.text_menucursor.y = this.text_menuitem_Talk.y + (this.currentmenuitem * GUI_MENUITEM_DISTANCE);
392 }
393
394 update() {
395 if ((this.inMenu) && (hasTimePassed(this.last_menustep, WAIT_MENUSTEP))) {
396 if (cursors.up.isDown)
397 {
398 this.currentmenuitem--;
399 if (this.currentmenuitem < MENUITEM_TALK) this.currentmenuitem = MENUITEM_TAKE;
400 this.actualizeCursorPosition();
401 }
402 else if (cursors.down.isDown)
403 {
404 this.currentmenuitem++;
405 if (this.currentmenuitem > MENUITEM_TAKE) this.currentmenuitem = MENUITEM_TALK;
406 this.actualizeCursorPosition();
407 }
408 if (game.input.keyboard.isDown(Phaser.Keyboard.ENTER)) {
409 logic.callMenu(this.currentmenuitem);
410 }
411 this.last_menustep = game.time.now;
412 }
413 if ((this.inTalk) && (game.input.keyboard.isDown(Phaser.Keyboard.ENTER)) && (hasTimePassed(this.last_talk, WAIT_TALK))) {
414 this.advanceTalk();
415 }
416 }
417 }
418
419 class GameLogic {
420
421 constructor() {
422 this.player = this.clara = this.carlos = this.saiki = this.peter = this.bianca = null;
423 this.gameinterface = new GameInterface(game, game.stage);
424 //this.doors = game.add.group(game.world, "doors");
425 this.last_menuselect = 0;
426 }
427
428 createObject(object) {
429 switch (object.type) {
430 case 'spawnpoint': this.createCharacter(object); break;
431 case 'door': this.createDoor(object); break;
432 case '': console.error("Object type is empty:", object); break;
433 default: console.error("Unknown object type:", object);
434 }
435 }
436
437 createCharacter(object) {
438 var newChar;
439 switch (object.name) {
440 case 'john':
441 newChar = new Player(object.x, object.y, 'player');
442 this.player = newChar;
443 this.player.enablePhysics();
444 game.camera.follow(this.player);
445 break;
446 case 'clara':
447 newChar = new NPC_Clara(object.x, object.y, 'clara', "Clara", "Clara Tnavelerri", 200);
448 this.clara = newChar;
449 break;
450 default:
451 console.error("Unknown character:", object);
452 }
453 newChar.name = object.name;
454 game.add.existing(newChar);
455 console.log(newChar);
456 }
457
458 createDoor(object) {
459 /* Calculate movement vector and correct position (32 = half tile width/height). */
460 var vector;
461 switch (object.rotation) {
462 case undefined:
463 case 0: vector = new Phaser.Point( -1, 0); object.x += 32; object.y -= 32; break;
464 case 90: vector = new Phaser.Point( 0, -1); object.x += 32; object.y += 32; break;
465 case 180: vector = new Phaser.Point( 1, 0); object.x -= 32; object.y += 32; break;
466 case 270: vector = new Phaser.Point( 0, 1); object.x -= 32; object.y -= 32; break;
467 default: console.error("Invalid rotation:", object.rotation);
468 }
469 this.doors.add(new Door(object.x, object.y, object.name, object.rotation, vector, object.properties.longpanel));
470 }
471
472 callMenu(menuitem) {
473 console.log("Menu callback received:", menuitem);
474 this.last_menuselect = game.time.now;
475 this.gameinterface.leaveMenu();
476 switch (menuitem) {
477 case MENUITEM_TALK:
478 this.player.interactablenpc.actionTalk();
479 break;
480 case MENUITEM_LEAVE:
481 this.player.interactablenpc.actionLeave();
482 this.player.unfreeze();
483 break;
484 case MENUITEM_TAKE:
485 if (this.player.interactablenpc.actionTake())
486 this.player.unfreeze();
487 break;
488 }
489 }
490
491 endTalk() {
492 if (this.player.interactablenpc) {
493 if (this.player.interactablenpc.endTalk()) {
494 this.gameinterface.npcMenu(this.player.interactablenpc);
495 }
496 else {
497 this.player.unfreeze();
498 this.last_menuselect = game.time.now;
499 }
500 }
501 }
502
503 openDoor(doorname) {
504 console.log("Opening doors.");
505 this.doors.children.forEach(function(o) { if (o.name == doorname) o.open(); });
506 }
507
508 closeDoor(doorname) {
509 this.doors.children.forEach(function(o) { if (o.name == doorname) o.close(); });
510 }
511
512 update() {
513 if ((game.input.keyboard.isDown(Phaser.Keyboard.ENTER)) && (hasTimePassed(this.last_menuselect, WAIT_MENUSTEP))) {
514 if ((this.player.interactablenpc) && (this.player.interactablenpc.interactable) && (!this.gameinterface.inMenu) && (!this.gameinterface.inTalk)) {
515 console.log("Starting interaction with", this.player.interactablenpc.fullname);
516 this.player.freeze();
517 this.gameinterface.npcMenu(this.player.interactablenpc);
518 }
519 }
520 }
521
522 }
523
524 class GamePlay extends Phaser.State {
525
526 constructor() {
527
528 super();
529 logic = new GameLogic();
530 game.world.updateOnlyExistingChildren = true;
531 //game.state.add('GameOver', GameOver, false);
532
533 }
534
535 preload() {
536
537 game.load.image('player', 'john.png');
538 game.load.image('clara', 'clara.png');
539 game.load.image('saiki', 'saiki.png');
540 game.load.image('tileset', 'tileset.png');
541 game.load.image('objects', 'objects.png');
542 game.load.tilemap('gamemap', 'tilemap.json', null, Phaser.Tilemap.TILED_JSON);
543
544 }
545
546 create() {
547
548 game.world.setBounds(0, 0, 800, 600);
549 game.stage.backgroundColor = '#000000';
550 game.physics.startSystem(Phaser.Physics.ARCADE);
551
552 var map = game.add.tilemap('gamemap');
553 map.addTilesetImage('tileset', 'tileset');
554 map.addTilesetImage('objects', 'objects');
555 var layer_floor = map.createLayer('floor');
556 layer_floor.resizeWorld();
557 this.layer_walls = map.createLayer('walls');
558 map.setCollisionBetween(1, 100, true, this.layer_walls);
559 this.layer_furniture = map.createLayer('furniture');
560 map.setCollisionBetween(1, 100, true, this.layer_furniture);
561
562 // FIXME: Don't create a group here like this, it's too ugly! Now I have it here due to the display order.
563 logic.doors = game.add.group(game.world, "doors");
564
565 map.objects['objects'].forEach(function(o) { logic.createObject(o); });
566 console.log(map.objects);
567
568 cursors = game.input.keyboard.createCursorKeys();
569 console.log(logic);
570
571 }
572
573 update() {
574
575 game.physics.arcade.collide(logic.player, this.layer_walls);
576 game.physics.arcade.collide(logic.player, this.layer_furniture);
577 logic.update();
578
579 }
580 }