Add Peter & Clara happiness conditions
[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 this.longpanel = longpanel;
47 if (!longpanel) {
48 this.anchor = new Phaser.Point(0.5, 0.5);
49 this.tilePosition = new Phaser.Point(-64, -64);
50 this.openvector = Phaser.Point.multiply(vector, new Phaser.Point(56, 56));
51 }
52 else {
53 this.width *= 2;
54 this.anchor = new Phaser.Point(0.75, 0.5);
55 this.tilePosition = new Phaser.Point(0, -64);
56 this.openvector = Phaser.Point.multiply(vector, new Phaser.Point(116, 116));
57 }
58 if (rotation === undefined) rotation = 0;
59 this.rotation = rotation * (Math.PI / 180);
60 this.closetween = game.add.tween(this).to({ x: this.position.x, y: this.position.y }, 1000, Phaser.Easing.Sinusoidal.InOut, false, 0, 0, false);
61 this.openposition = Phaser.Point.add(this.position, this.openvector);
62 this.opentween = game.add.tween(this).to({ x: this.openposition.x, y: this.openposition.y }, 1000, Phaser.Easing.Sinusoidal.InOut, false, 0, 0, false);
63 this.isOpen = false;
64 game.physics.arcade.enable(this);
65 this.body.immovable = true;
66 this.setBody(rotation);
67 }
68
69 setBody(rotation) {
70 switch(rotation) {
71 case 0:
72 case 180:
73 this.body.setSize(this.width, 10, this.longpanel * (rotation / 180) * 64, 27);
74 break;
75 case 90:
76 case 270:
77 this.body.setSize(10, this.width, 27 + (this.longpanel * 64), this.longpanel * ((rotation - 270) / 180) * 64);
78 break;
79 default:
80 console.log("Unable to set body due to unknown rotation:", rotation);
81 }
82 }
83
84 open() {
85 if (!this.isOpen) {
86 this.opentween.start();
87 this.isOpen = true;
88 }
89 }
90
91 close() {
92 if (this.isOpen) {
93 this.closetween.start();
94 this.isOpen = false;
95 }
96 }
97
98 update() {
99 game.debug.body(this);
100 }
101 }
102
103 class Player extends Phaser.Sprite {
104 constructor(x, y) {
105 super(game, x, y, 'player');
106 this.y -= this.height;
107 this.shortname = "John";
108 this.fullname = "John Evals";
109 this.offerInteraction(null);
110 this.unfreeze();
111 }
112
113 enablePhysics() {
114 game.physics.arcade.enable(this);
115 this.body.bounce.y = 0.2;
116 this.body.bounce.x = 0.2;
117 this.body.collideWorldBounds = true;
118 }
119
120 control() {
121 if ((this.alive) && (!this.freezed))
122 {
123 if (cursors.left.isDown)
124 {
125 this.body.velocity.x = -PLAYER_SPEED;
126 }
127 else if (cursors.right.isDown)
128 {
129 this.body.velocity.x = PLAYER_SPEED;
130 }
131 if (cursors.up.isDown)
132 {
133 this.body.velocity.y = -PLAYER_SPEED;
134 }
135 else if (cursors.down.isDown)
136 {
137 this.body.velocity.y = PLAYER_SPEED;
138 }
139 }
140 }
141
142 update() {
143 this.body.velocity.x = this.body.velocity.y = 0;
144 this.control();
145 }
146
147 freeze() {
148 this.freezed = true;
149 }
150
151 unfreeze() {
152 this.freezed = false;
153 }
154
155 offerInteraction(npc) {
156 this.interactablenpc = npc;
157 }
158
159 takeMe(npc) {
160 this.loadTexture(npc.key);
161 npc.kill();
162 this.disguise = npc;
163 logic.gameinterface.dropNoticeTimeout(Phaser.Timer.SECOND * 5, "You are now identified as " + npc.fullname + "!");
164 }
165 }
166
167 class GameNPC extends Phaser.Sprite {
168 constructor(x, y, key, shortname, fullname, interaction_distance) {
169 super(game, x, y, key);
170 this.y -= this.height;
171 this.shortname = shortname;
172 this.fullname = fullname;
173 this.interaction_distance = interaction_distance;
174 this.interactable = false;
175 this.talkcount = 0;
176 this.happy = false;
177 }
178
179 kill() {
180 super.kill();
181 this.exists = false;
182 this.happy = false; // Dead people are not happy.
183 }
184
185 update() {
186 super.update();
187 if ((!this.interactable) && (game.physics.arcade.distanceBetween(this, logic.player) < this.interaction_distance)) {
188 logic.gameinterface.dropNotice("(ENTER) Interact with " + this.shortname + "!");
189 logic.player.offerInteraction(this);
190 this.interactable = true;
191 }
192 else if ((this.interactable) && (game.physics.arcade.distanceBetween(this, logic.player) > this.interaction_distance)) {
193 logic.gameinterface.clearNotice();
194 logic.player.offerInteraction(null);
195 this.interactable = false;
196 }
197 if ((this.teleport_instructions) && (game.physics.arcade.distanceBetween(this, logic.player) > (game.width + 200))) {
198 this.position = this.teleport_instructions.newPosition;
199 this.teleport_instructions.callback(this.teleport_instructions.callbackValue);
200 this.teleport_instructions = null;
201 }
202 }
203
204 actionTalk() {
205 this.talkcount++;
206 }
207
208 actionLeave() {
209 }
210
211 actionTake() {
212 logic.player.offerInteraction(null);
213 logic.player.takeMe(this);
214 return true;
215 }
216
217 endTalk() {
218 return true;
219 }
220
221 makeHappy() {
222 this.happy = true;
223 console.log(this.shortname, "is happy!");
224 }
225
226 offscreenTeleportation(newPosition, callback, callbackValue) {
227 this.teleport_instructions = { newPosition: newPosition, callback: callback, callbackValue: callbackValue };
228 console.log("Offscreen teleport request registered:", this.teleport_instructions);
229 }
230 }
231
232 class NPC_Clara extends GameNPC {
233 actionTalk() {
234 switch (this.talkcount) {
235 case 0:
236 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "What a morning..." },
237 { actor: logic.player, text: "Hi Clara! What happened?" },
238 { 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." },
239 { actor: logic.player, text: "Hmm... I'll look into it. Maybe it's just that everyone is fired." },
240 { actor: this, text: "Haha! Wouldn't joke about this, though, due to the recent layoffs." },
241 { actor: logic.player, text: "Well, maybe if we dare to joke about it, it won't happen to us..." },
242 { actor: this, text: "Wish it worked like that... Is it some superstition like the belief that having an umbrella with you prevents rain?" },
243 { actor: logic.player, text: "Nah, that actually works; it's not a superstition, but Murphy's Law!" },
244 { actor: this, text: "If you say so..." },
245 { actor: this, text: "Anyway, I logged you in and opened the door for you." } ] ));
246 logic.openDoor("cutedoor");
247 break;
248 case 1:
249 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "John, have you ever thought about losing your employee card?" },
250 { actor: logic.player, text: "Yeah, you'd just get a new one." },
251 { actor: this, text: "You don't understand me, John!" },
252 { actor: this, text: "..." },
253 { actor: this, text: "I mean... Losing it for real..." },
254 { actor: this, text: "Doesn't it feel like it's the culmination of your being?" },
255 { actor: this, text: "If I had no card, would I still exist?" },
256 { actor: logic.player, text: "..." },
257 { actor: logic.player, text: "You sound very philosophical today." } ] ));
258 break;
259 case 2:
260 case 3:
261 case 4:
262 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Would you like to hear a random fun fact?" },
263 { actor: logic.player, text: "Of course!" },
264 { 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," },
265 { 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" },
266 { actor: this, text: "into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem" },
267 { actor: this, text: "Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." },
268 { actor: logic.player, text: "That's very interesting, I didn't know that!" },
269 { actor: logic.player, text: "Thanks for sharing, Clara!" } ] ));
270 break;
271 case 5:
272 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Would you like to hear a random fun fact?" },
273 { actor: logic.player, text: "Of course!..." },
274 { actor: logic.player, text: "But first..." },
275 { actor: this, text: "What is it?" },
276 { actor: logic.player, text: "..." },
277 { actor: logic.player, text: "Never mind, go on with what you wanted to say." },
278 { actor: this, text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry..." },
279 { actor: logic.player, text: "I knew that already..." },
280 { actor: logic.player, text: "But..." },
281 { actor: logic.player, text: "Would you go out on a date with me?" },
282 { actor: this, text: "..." },
283 { actor: this, text: "No." } ] ));
284 break;
285 default:
286 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "..." } ] ));
287 }
288 super.actionTalk();
289 }
290
291 actionLeave() {
292 logic.gameinterface.dropNotice(this.shortname + ": Have a great day, John!");
293 }
294
295 actionTake() {
296 logic.openDoor("cutedoor");
297 return super.actionTake();
298 }
299
300 endTalk() {
301 return (this.talkcount < 6);
302 }
303 }
304
305 class NPC_Carlos extends GameNPC {
306 actionTalk() {
307 if (logic.clara.alive) {
308 switch (this.talkcount) {
309 case 0:
310 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Good morning, Carlos! What's up?" },
311 { actor: this, text: "Hey, John! Not too much, I'm just trying to sell some shit to this new client." },
312 { actor: logic.player, text: "And so? Is it hard to convince them?" },
313 { actor: this, text: "Of course, they don't want to buy. It's shit." },
314 { actor: this, text: "I mean, who would want to buy shit?" },
315 { actor: logic.player, text: "Yeah. Relatable... But why are you trying to sell shit? I mean, why not one of our cutting edge technologies?" },
316 { actor: this, text: "They couldn't afford it." },
317 { actor: logic.player, text: "Then why are you trying to sell them... anything at all? They'd better off with a cheaper company." },
318 { actor: this, text: "Company policies. Plus the management instructed me to sell to them, no matter what." },
319 { actor: logic.player, text: "That sucks... Well... good luck!" },
320 { actor: this, text: "Thanks..." } ] ));
321 logic.closeDoor("cutedoor");
322 logic.openDoor("carlosdoor");
323 break;
324 case 1:
325 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "*Sigh.* I'll probably not work here for long, if I can't sell this shit to the customer." },
326 { actor: this, text: "These layoffs are terrifying. They let go of anyone for even the slightest mistake." },
327 { actor: logic.player, text: "But it's not a mistake when our client doesn't buy something they don't need." },
328 { actor: this, text: "Tell this to my boss..." } ] ));
329 break;
330 case 2:
331 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "You know, generally, I think it sucks being a child. One thing I miss though, if you're a child, you're allowed to be silly." },
332 { actor: this, text: "..." },
333 { actor: this, text: "For example, when my Dad told me that semen is the seed of animals, I theoretized, if I'd bury some pig semen in the garden, a pig would grow out of it..." },
334 { actor: this, text: "I was totally fascinated by the thought, because I really wanted to have a pet pig, but Dad wouldn't let me have one." },
335 { actor: this, text: "So I thought I could grow my own pig in the garden from pig seeds!" },
336 { actor: logic.player, text: "Interesting. Where are you going with this?" },
337 { actor: this, text: "I don't know... sometimes I feel like I'd rather sell pig semen to our clients, heh!" },
338 { actor: this, text: "Uhm..." },
339 { actor: this, text: "Totally don't tell this to Saiki!" } ] ));
340 this.pigsemen = true;
341 break;
342 case 3:
343 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Weren't you heading to the IT department to the right?" },
344 { actor: this, text: "You know... the green department which looks way better than blue!" },
345 { actor: this, text: "..." },
346 { actor: this, text: "Damn, why do I have to work in blue? I just feel... so blue about it." } ] ));
347 break;
348 case 4:
349 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Weren't you heading to the conveniently green department to the right? I guess Saiki is waiting for you." },
350 { actor: this, text: "You're so lucky that you can work with her every day!" } ] ));
351 break;
352 default:
353 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Guess you'll stay here then. It's cool, you're great company anyway." } ] ));
354 }
355 }
356 else {
357 switch (this.talkcount) {
358 case 0:
359 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Good morning, Carlos!" },
360 { actor: this, text: "Whoa, Clara! You have changed so much! I barely recognize you. What's up?" },
361 { actor: logic.player, text: "I need to talk to Saiki at the IT department, but... my card doesn't allow me to enter there." },
362 { actor: this, text: "Well... I can certainly help you with that..." },
363 { actor: this, text: "..." } ] ));
364 break;
365 case 1:
366 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Carlos. You promised to help me." },
367 { actor: this, text: "Uhm... It's just... Please give me 5 mins to finish this e-mail." },
368 { actor: logic.player, text: "I don't have much time, Carlos." },
369 { actor: this, text: "See, it's just 5 mins, please be patient. I have a chocolate bar in the fridge. I give it to you. Feel free to have it meanwhile." },
370 { actor: logic.player, text: "..." } ] ));
371 break;
372 case 2:
373 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Time is up, Carlos!" },
374 { actor: this, text: "Please give me just another min." },
375 { actor: logic.player, text: "Fine. Please just give me your card and I'll help myself!" },
376 { actor: this, text: "But... you know that company policies forbid that, Clara! Please just be a little more patient!" },
377 { actor: logic.player, text: "You will give me your card now or you will regret!" },
378 { actor: this, text: "...?" },
379 { actor: this, text: "You're not gonna treat me like this. Forget it!" },
380 { actor: logic.player, text: "You'll not work here tomorrow." } ] ));
381 break;
382 default:
383 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "..." } ] ));
384 }
385 }
386 super.actionTalk();
387 }
388
389 actionLeave() {
390 if (logic.clara.alive) {
391 logic.gameinterface.dropNotice(this.shortname + ": Bye, John! Pass on my greetings to Saiki!");
392 }
393 }
394
395 actionTake() {
396 logic.closeDoor("cutedoor");
397 logic.openDoor("carlosdoor");
398 return super.actionTake();
399 }
400
401 endTalk() {
402 return (logic.clara.alive) || (this.talkcount < 3);
403 }
404 }
405
406 class NPC_Saiki extends GameNPC {
407 actionTalk() {
408 if (logic.carlos.alive) {
409 switch (this.talkcount) {
410 case 0:
411 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Hi Saiki!" },
412 { actor: this, text: "Hi John! Glad you arrived, here's some work for you!" },
413 { actor: logic.player, text: "Awesome! What is it?" },
414 { actor: this, text: "Haha, joking! Actually, our queue is suspiciously empty for today." },
415 { actor: logic.player, text: "Yeah... That's really suspicious!" },
416 { actor: logic.player, text: "Maybe I can get home today in time?" },
417 { actor: this, text: "You mean, you wouldn't spend the mandatory free extra working hours?" },
418 { actor: this, text: "You steal from the company! The company needs your unpaid hours of work! Otherwise, how would we finance the trash can I ordered for the department?" },
419 { actor: this, text: "*Whispers with wink.* I hope you get my sarcasm." },
420 { actor: logic.player, text: "Haha! I always get it, Saiki!" } ] ));
421 logic.closeDoor("carlosdoor");
422 logic.openDoor("saikidoor");
423 logic.openDoor("peterdoor");
424 break;
425 case 1:
426 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Now that I think about it, there's still one thing. You could look into why the access control system is acting so funny today." },
427 { actor: this, text: "Clara has to enter people manually." },
428 { actor: logic.player, text: "Yeah, I'll definitely check that." } ] ));
429 break;
430 case 2:
431 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "So... what's up with the trash can?" },
432 { actor: this, text: "I'm always up for such deep conversation" },
433 { actor: this, text: "about trash cans." },
434 { actor: this, text: "While we only have one trash can, other departments seem to be abundant of them. And ours is always filled. So I requested an additional trash can." },
435 { actor: this, text: "Now the ticket is in PENDING status. The latest work note is from two months ago, telling there is no budget at the moment." },
436 { actor: logic.player, text: "Hmm... Maybe it's too much to ask. Can we take one from another department?" },
437 { actor: this, text: "No, everyone holds on to their trash cans." },
438 { actor: logic.player, text: "You certainly did your research." },
439 { actor: this, text: "Of course. It's an important subject." } ] ));
440 break;
441 case 3:
442 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "My parents named me after a Japanese rock singer." },
443 { actor: logic.player, text: "Cool! Which band?" },
444 { actor: this, text: "I don't remember the band name. But the particular singer has a blue rose in her hair most of the time." } ] ));
445 break;
446 default:
447 if ((logic.carlos.pigsemen) && (this.talkcount == 4)) {
448 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Did you know that Carlos thought he can grow a pig by burying pig semen in the ground? What do you think about this?" },
449 { actor: this, text: "Hmm..." },
450 { actor: this, text: "I guess it didn't work." },
451 { actor: this, text: "So do you want to know what I think about this?" },
452 { actor: this, text: "..." },
453 { actor: this, text: "IT'S BRILLIANT!!!" },
454 { actor: this, text: "You know, it really sparkled my artistic vision! Now I must draw a pig with its legs rooted into the ground!" },
455 { actor: logic.player, text: "That's gross. Poor pig." },
456 { actor: this, text: "Indeed. But this is the point. He's stuck in one place, cannot move. Like you in life. So you feel sympathy for him." },
457 { actor: logic.player, text: "You have a point." },
458 { actor: this, text: "I think I'll catch Carlos and talk to him. I always liked that guy but I didn't know he's such imaginative." } ] ));
459 this.offscreenTeleportation(new Phaser.Point(logic.carlos.x, logic.carlos.y + 64), this.tpDone, this);
460 }
461 else {
462 if (Phaser.Math.random() < 0.5) {
463 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "I've found an orchestrated rendition of „Potential for Anything”. You'll have to check it out, it's breathtaking!" } ] ));
464 }
465 else {
466 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "I don't like that our department is green. The blue one, where Carlos works, looks much better." } ] ));
467 }
468 }
469 }
470 }
471 else {
472 switch (this.talkcount) {
473 case 0:
474 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "I love you, Saiki!" },
475 { actor: this, text: "Hi Carlos..." },
476 { actor: logic.player, text: "I'm infatuated by the mere thought of you." },
477 { actor: this, text: "Well... it sounds pretty much creepy." },
478 { actor: logic.player, text: "Will you go out on a date with me?" },
479 { actor: this, text: "..." },
480 { actor: this, text: "Sorry to hurt your feelings, but... even though I like you... Your sudden, unprecedented confession makes me scared." },
481 { actor: this, text: "Especially with that facial expression." },
482 { actor: this, text: "I mean... you're not even joking." },
483 { actor: this, text: "So sorry... I won't date you." } ] ));
484 break;
485 case 1:
486 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "I thought I could impress you." },
487 { actor: this, text: "I'm sorry." } ] ));
488 break;
489 case 2:
490 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "You will give me your card." },
491 { actor: this, text: "Geez, what for? Go back to your department and work!" },
492 { actor: logic.player, text: "I need it." },
493 { actor: this, text: "..." } ] ));
494 break;
495 default:
496 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Don't you have anything better to do, Carlos?" } ] ));
497 }
498 }
499 super.actionTalk();
500 }
501
502 actionLeave() {
503 if (logic.carlos.alive) {
504 logic.gameinterface.dropNotice(this.shortname + ": It was nice talking to you!");
505 }
506 }
507
508 actionTake() {
509 logic.closeDoor("carlosdoor");
510 logic.openDoor("saikidoor");
511 logic.openDoor("peterdoor");
512 logic.carlos.kill();
513 return super.actionTake();
514 }
515
516 endTalk() {
517 return (logic.carlos.alive);
518 }
519
520 tpDone(value) {
521 console.log("Woot-woot! Teleport complete:", value);
522 value.makeHappy();
523 logic.carlos.makeHappy();
524 // FIXME: Need to find a prettier way than this.
525 logic.carlos.update = function() { }; // Disable Carlos' interaction. (Saiki will talk for him.)
526 }
527 }
528
529 class NPC_Peter extends GameNPC {
530 actionTalk() {
531 if (logic.carlos.alive) {
532 switch (this.talkcount) {
533 case 0:
534 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Hi Peter! What's up?" },
535 { actor: this, text: "Hi John! Bianca has locked herself in the large meeting room." },
536 { actor: logic.player, text: "Why would she do that?" },
537 { actor: this, text: "I have no idea." },
538 { actor: this, text: "Normally, I would give a fuck about what she's doing and her problems, but I want to hold a meeting in an hour." },
539 { actor: logic.player, text: "That was rude. I think you should care more about your colleagues." },
540 { actor: this, text: "Ahh... You're right. I didn't mean it. I'm just so frustrated about all the shit going on here." } ] ));
541 logic.closeDoor("saikidoor");
542 logic.openDoor("peterdoor");
543 logic.openDoor("biancadoor");
544 break;
545 case 1:
546 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "See... Would you talk to Bianca? We don't understand each other pretty well. That's why I'd rather wouldn't make a shot." } ] ));
547 break;
548 default:
549 if ((logic.clara.talkcount >= 5) && (this.talkcount == 2)) {
550 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Do you know Clara from the Reception?" },
551 { actor: this, text: "Of course I do." },
552 { actor: logic.player, text: "I think she's mad at me..." },
553 { actor: this, text: "Why would she?" },
554 { actor: logic.player, text: "I kind of... asked her out." },
555 { actor: logic.player, text: "She said no." },
556 { actor: this, text: "What the heck, dude?" },
557 { actor: this, text: "..." },
558 { actor: this, text: "You should know it's against the company etiquette..." },
559 { actor: this, text: "Regardless, I don't think she's really mad. You're a cool guy." },
560 { actor: logic.player, text: "I hope this will be sorted out... I totally accept her answer, but I wouldn't like her to have negative feelings towards me." } ] ));
561 this.offscreenTeleportation(new Phaser.Point(logic.clara.x - 160, logic.clara.y), this.tpDone, this);
562 }
563 else {
564 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "I'm kind of busy, man." } ] ));
565 }
566 }
567 }
568 else {
569 switch (this.talkcount) {
570 case 0:
571 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "What a wonderful day, Peter!" },
572 { actor: this, text: "Yikes! Saiki! You scared me!" },
573 { actor: logic.player, text: "I didn't even say \"boo\"." },
574 { actor: this, text: "Are you OK? You look horrible! You should really see a doctor." },
575 { actor: logic.player, text: "I'm fine, Peter. And you?" },
576 { actor: this, text: "No... Not at all." },
577 { actor: logic.player, text: "I'm sorry to hear that..." } ] ));
578 break;
579 case 1:
580 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "Don't come closer to me!" },
581 { actor: logic.player, text: "Why?" },
582 { actor: this, text: "I don't mean to offend you, but... you really creep me out..." },
583 { actor: logic.player, text: "Why do you say that?" },
584 { actor: this, text: "I honestly can't tell what's wrong with you, but... you look like that ghost girl from The Ring!" } ] ));
585 break;
586 case 2:
587 logic.gameinterface.talk(new Dialogue( [ { actor: logic.player, text: "Now that was pretty much racist..." },
588 { actor: logic.player, text: "Just because I have a Japanese name and long black hair, that makes me immediately look like the Japanese girl from Ring when I'm sick?" },
589 { actor: this, text: "Geez... that's not even a Japanese movie! And the actress is American..." },
590 { actor: logic.player, text: "The original movie is Japanese." },
591 { actor: this, text: "I don't care... Just... keep distance, please!" } ] ));
592 break;
593 default:
594 if (Phaser.Math.random() < 0.5) {
595 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "STAY AWAY FROM ME!" } ] ));
596 }
597 else {
598 logic.gameinterface.talk(new Dialogue( [ { actor: this, text: "DON'T COME ANY CLOSER!" } ] ));
599 }
600 }
601 }
602 super.actionTalk();
603 }
604
605 actionTake() {
606 logic.closeDoor("saikidoor");
607 logic.openDoor("peterdoor");
608 logic.openDoor("biancadoor");
609 return super.actionTake();
610 }
611
612 endTalk() {
613 return (logic.saiki.alive) && (this.talkcount < 3);
614 }
615
616 tpDone(value) {
617 console.log("Woot-woot! Teleport complete:", value);
618 value.makeHappy();
619 logic.clara.makeHappy();
620 // FIXME: Need to find a prettier way than this.
621 logic.clara.update = function() { }; // Disable Clara's interaction. (Peter will talk for him.)
622 }
623 }
624
625 class NPC_Bianca extends GameNPC {
626 actionTake() {
627 // All doors open for you if you have all the cards.
628 logic.openDoor("cutedoor");
629 logic.openDoor("carlosdoor");
630 logic.openDoor("saikidoor");
631 return super.actionTake();
632 }
633 }
634
635
636 class GameInterface extends Phaser.Group {
637 constructor(game, parent) {
638 super(game, parent, 'GUI', false, false, 0);
639 this.back_notice = new Phaser.Graphics(game, 0, game.height - 50);
640 this.back_notice.beginFill(0x000000);
641 this.back_notice.drawRect(0, 0, game.width, 30);
642 this.back_notice.endFill();
643 this.add(this.back_notice);
644 this.text_notice = new Phaser.Text(game, 0, this.back_notice.y, null, { align: 'left', fill: 'white', font: 'Ubuntu Mono', fontSize: 18, fontWeight: 'bold' });
645 this.text_notice.anchor.setTo(-0.5, -0.2);
646 this.add(this.text_notice);
647 this.clearNotice();
648
649 this.back_menu = new Phaser.Graphics(game, 150, (game.height / 2) + 40);
650 this.back_menu.beginFill(0x000000);
651 this.back_menu.drawRect(0, 0, 155, 105);
652 this.back_menu.endFill();
653 this.back_menu.lineStyle(3, Phaser.Color.YELLOW, 1);
654 this.back_menu.moveTo(10, 35);
655 this.back_menu.lineTo(this.back_menu.width - 10, 35);
656 this.add(this.back_menu);
657 var style = { align: 'left', fill: 'yellow', font: 'Ubuntu Mono', fontSize: 22, fontWeight: 'bold' };
658 this.text_menutitle = new Phaser.Text(game, this.back_menu.x, this.back_menu.y, null, style);
659 this.text_menutitle.anchor.setTo(-0.2, -0.2);
660 style = { align: 'left', fill: 'white', font: 'Ubuntu Mono', fontSize: 18, fontWeight: 'bold' };
661 this.text_menuitem_Talk = new Phaser.Text(game, this.back_menu.x + 35, this.back_menu.y + 40, "Talk", style);
662 this.text_menuitem_Leave = new Phaser.Text(game, this.back_menu.x + 35, this.text_menuitem_Talk.y + GUI_MENUITEM_DISTANCE, "Leave", style);
663 this.text_menuitem_Take = new Phaser.Text(game, this.back_menu.x + 35, this.text_menuitem_Leave.y + GUI_MENUITEM_DISTANCE, "Take ID", style);
664 style.fill = 'yellow';
665 this.text_menucursor = new Phaser.Text(game, this.back_menu.x + 15, this.text_menuitem_Talk.y, "→", style);
666 this.add(this.text_menutitle);
667 this.add(this.text_menuitem_Talk);
668 this.add(this.text_menuitem_Leave);
669 this.add(this.text_menuitem_Take);
670 this.add(this.text_menucursor);
671 this.leaveMenu();
672 this.last_menustep = 0;
673
674 this.back_talk = new Phaser.Graphics(game, 100, game.height - 120);
675 this.back_talk.beginFill(0x000000);
676 this.back_talk.drawRect(0, 0, game.width - 200, 105);
677 this.back_talk.endFill();
678 this.back_talk.lineStyle(3, Phaser.Color.YELLOW, 1);
679 this.back_talk.moveTo(10, 35);
680 this.back_talk.lineTo(this.back_talk.width - 10, 35);
681 this.add(this.back_talk);
682 style = { align: 'left', fill: 'yellow', font: 'Ubuntu Mono', fontSize: 22, fontWeight: 'bold' };
683 this.text_talktitle = new Phaser.Text(game, this.back_talk.x + 15, this.back_talk.y + 6, null, style);
684 //this.text_talktitle.anchor.setTo(-0.2, -0.2);
685 this.add(this.text_talktitle);
686 this.strikethrough_talktitle = new Phaser.Graphics(game, this.back_talk.x, this.back_talk.y);
687 this.strikethrough_talktitle.lineStyle(3, Phaser.Color.RED, 1);
688 this.strikethrough_talktitle.moveTo(12, 20);
689 this.strikethrough_talktitle.lineTo(67, 20);
690 this.add(this.strikethrough_talktitle);
691 style = { align: 'left', fill: 'white', font: 'Ubuntu Mono', fontSize: 18, fontWeight: 'bold' };
692 this.text_talk = new Phaser.Text(game, this.back_talk.x + 35, this.back_talk.y + 40, null, style);
693 this.text_talk.wordWrap = true;
694 this.text_talk.wordWrapWidth = this.back_talk.width - 70;
695 this.text_talk.lineSpacing = -5;
696 this.add(this.text_talk);
697 this.leaveTalk();
698 this.last_talk = 0;
699 }
700
701 dropNotice(text) {
702 this.text_notice.text = text;
703 this.back_notice.visible = true;
704 this.text_notice.visible = true;
705 }
706
707 dropNoticeTimeout(timeout, text) {
708 this.dropNotice(text);
709 game.time.events.add(timeout, this.clearNotice, this);
710 }
711
712 clearNotice() {
713 this.back_notice.visible = false;
714 this.text_notice.visible = false;
715 }
716
717 npcMenu(npc) {
718 if (!this.inMenu) {
719 this.inMenu = true;
720 this.clearNotice();
721 this.text_menutitle.text = npc.shortname;
722 this.back_menu.visible = true;
723 this.text_menutitle.visible = true;
724 this.text_menuitem_Talk.visible = true;
725 this.text_menuitem_Leave.visible = true;
726 this.text_menuitem_Take.visible = true;
727 this.currentmenuitem = MENUITEM_TALK;
728 this.actualizeCursorPosition();
729 this.text_menucursor.visible = true;
730 this.last_menustep = game.time.now;
731 }
732 }
733
734 leaveMenu() {
735 this.back_menu.visible = false;
736 this.text_menutitle.visible = false;
737 this.text_menuitem_Talk.visible = false;
738 this.text_menuitem_Leave.visible = false;
739 this.text_menuitem_Take.visible = false;
740 this.text_menucursor.visible = false;
741 this.inMenu = false;
742 }
743
744 talk(dialogue) {
745 this.inTalk = true;
746 this.dialogue = dialogue;
747 this.advanceTalk();
748 this.back_talk.visible = true;
749 this.text_talktitle.visible = true;
750 this.text_talk.visible = true;
751 }
752
753 leaveTalk() {
754 this.inTalk = false;
755 this.dialogue = null;
756 this.back_talk.visible = false;
757 this.text_talktitle.visible = false;
758 this.strikethrough_talktitle.visible = false;
759 this.text_talk.visible = false;
760 }
761
762 advanceTalk() {
763 console.log(this.dialogue);
764 console.log(this.dialogue.actual());
765 var actualdialogue = this.dialogue.actual();
766 if (actualdialogue) {
767 if (actualdialogue.actor.disguise) {
768 this.text_talktitle.text = actualdialogue.actor.shortname + " " + actualdialogue.actor.disguise.shortname;
769 this.strikethrough_talktitle.visible = true;
770 }
771 else {
772 this.text_talktitle.text = actualdialogue.actor.shortname;
773 this.strikethrough_talktitle.visible = false;
774 }
775 this.text_talk.text = actualdialogue.text;
776 this.dialogue.advance();
777 }
778 else {
779 this.leaveTalk();
780 logic.endTalk();
781 }
782 this.last_talk = game.time.now;
783 }
784
785 actualizeCursorPosition() {
786 this.text_menucursor.y = this.text_menuitem_Talk.y + (this.currentmenuitem * GUI_MENUITEM_DISTANCE);
787 }
788
789 update() {
790 if ((this.inMenu) && (hasTimePassed(this.last_menustep, WAIT_MENUSTEP))) {
791 if (cursors.up.isDown)
792 {
793 this.currentmenuitem--;
794 if (this.currentmenuitem < MENUITEM_TALK) this.currentmenuitem = MENUITEM_TAKE;
795 this.actualizeCursorPosition();
796 }
797 else if (cursors.down.isDown)
798 {
799 this.currentmenuitem++;
800 if (this.currentmenuitem > MENUITEM_TAKE) this.currentmenuitem = MENUITEM_TALK;
801 this.actualizeCursorPosition();
802 }
803 if (game.input.keyboard.isDown(Phaser.Keyboard.ENTER)) {
804 logic.callMenu(this.currentmenuitem);
805 }
806 this.last_menustep = game.time.now;
807 }
808 if ((this.inTalk) && (game.input.keyboard.isDown(Phaser.Keyboard.ENTER)) && (hasTimePassed(this.last_talk, WAIT_TALK))) {
809 this.advanceTalk();
810 }
811 }
812 }
813
814 class GameLogic {
815
816 constructor() {
817 this.player = this.clara = this.carlos = this.saiki = this.peter = this.bianca = null;
818 this.gameinterface = new GameInterface(game, game.stage);
819 //this.doors = game.add.group(game.world, "doors");
820 this.last_menuselect = 0;
821 }
822
823 createObject(object) {
824 switch (object.type) {
825 case 'spawnpoint': this.createCharacter(object); break;
826 case 'door': this.createDoor(object); break;
827 case '': console.error("Object type is empty:", object); break;
828 default: console.error("Unknown object type:", object);
829 }
830 }
831
832 createCharacter(object) {
833 var newChar;
834 switch (object.name) {
835 case 'john':
836 newChar = new Player(object.x, object.y, 'player');
837 this.player = newChar;
838 this.player.enablePhysics();
839 game.camera.follow(this.player);
840 break;
841 case 'clara':
842 newChar = new NPC_Clara(object.x, object.y, 'clara', "Clara", "Clara Tnavelerri", 200);
843 this.clara = newChar;
844 break;
845 case 'carlos':
846 newChar = new NPC_Carlos(object.x, object.y, 'carlos', "Carlos", "Carlos Elbacalper", 150);
847 this.carlos = newChar;
848 break;
849 case 'saiki':
850 newChar = new NPC_Saiki(object.x, object.y, 'saiki', "Saiki", "Saiki Ytpme", 150);
851 this.saiki = newChar;
852 break;
853 case 'peter':
854 newChar = new NPC_Peter(object.x, object.y, 'peter', "Peter", "Peter Tluaf", 225);
855 this.peter = newChar;
856 break;
857 case 'bianca':
858 newChar = new NPC_Bianca(object.x, object.y, 'bianca', "Bianca", "Bianca Gnihton", 150);
859 this.bianca = newChar;
860 break;
861 default:
862 console.error("Unknown character:", object);
863 }
864 newChar.name = object.name;
865 game.add.existing(newChar);
866 console.log(newChar);
867 }
868
869 createDoor(object) {
870 /* Calculate movement vector and correct position (32 = half tile width/height). */
871 var vector;
872 switch (object.rotation) {
873 case undefined:
874 case 0: vector = new Phaser.Point( -1, 0); object.x += 32; object.y -= 32; break;
875 case 90: vector = new Phaser.Point( 0, -1); object.x += 32; object.y += 32; break;
876 case 180: vector = new Phaser.Point( 1, 0); object.x -= 32; object.y += 32; break;
877 case 270: vector = new Phaser.Point( 0, 1); object.x -= 32; object.y -= 32; break;
878 default: console.error("Invalid rotation:", object.rotation);
879 }
880 this.doors.add(new Door(object.x, object.y, object.name, object.rotation, vector, object.properties.longpanel));
881 }
882
883 callMenu(menuitem) {
884 console.log("Menu callback received:", menuitem);
885 this.last_menuselect = game.time.now;
886 this.gameinterface.leaveMenu();
887 switch (menuitem) {
888 case MENUITEM_TALK:
889 this.player.interactablenpc.actionTalk();
890 break;
891 case MENUITEM_LEAVE:
892 this.player.interactablenpc.actionLeave();
893 this.player.unfreeze();
894 break;
895 case MENUITEM_TAKE:
896 if (this.player.interactablenpc.actionTake())
897 this.player.unfreeze();
898 break;
899 }
900 }
901
902 endTalk() {
903 if (this.player.interactablenpc) {
904 if (this.player.interactablenpc.endTalk()) {
905 this.gameinterface.npcMenu(this.player.interactablenpc);
906 }
907 else {
908 this.player.unfreeze();
909 this.last_menuselect = game.time.now;
910 }
911 }
912 }
913
914 openDoor(doorname) {
915 this.doors.children.forEach(function(o) { if (o.name == doorname) o.open(); });
916 }
917
918 closeDoor(doorname) {
919 this.doors.children.forEach(function(o) { if (o.name == doorname) o.close(); });
920 }
921
922 checkAllHappy() {
923 return (this.clara.happy) && (this.carlos.happy) && (this.saiki.happy);
924 }
925
926 update() {
927 if ((game.input.keyboard.isDown(Phaser.Keyboard.ENTER)) && (hasTimePassed(this.last_menuselect, WAIT_MENUSTEP))) {
928 if ((this.player.interactablenpc) && (this.player.interactablenpc.interactable) && (!this.gameinterface.inMenu) && (!this.gameinterface.inTalk)) {
929 console.log("Starting interaction with", this.player.interactablenpc.fullname);
930 this.player.freeze();
931 this.gameinterface.npcMenu(this.player.interactablenpc);
932 }
933 }
934 }
935
936 }
937
938 class GamePlay extends Phaser.State {
939
940 constructor() {
941
942 super();
943 logic = new GameLogic();
944 game.world.updateOnlyExistingChildren = true;
945 game.state.add('GameOver', GameOver, false);
946
947 }
948
949 preload() {
950
951 game.load.image('player', 'john.png');
952 game.load.image('clara', 'clara.png');
953 game.load.image('carlos', 'carlos.png');
954 game.load.image('saiki', 'saiki.png');
955 game.load.image('peter', 'peter.png');
956 game.load.image('bianca', 'bianca.png');
957 game.load.image('tileset', 'tileset.png');
958 game.load.image('objects', 'objects.png');
959 game.load.tilemap('gamemap', 'tilemap.json', null, Phaser.Tilemap.TILED_JSON);
960
961 }
962
963 create() {
964
965 game.world.setBounds(0, 0, 800, 600);
966 game.stage.backgroundColor = '#000000';
967 game.physics.startSystem(Phaser.Physics.ARCADE);
968 console.log("Debug enabled:", !game.debug.isDisabled);
969
970 var map = game.add.tilemap('gamemap');
971 map.addTilesetImage('tileset', 'tileset');
972 map.addTilesetImage('objects', 'objects');
973 var layer_floor = map.createLayer('floor');
974 layer_floor.resizeWorld();
975 this.layer_walls = map.createLayer('walls');
976 map.setCollisionBetween(1, 100, true, this.layer_walls);
977 this.layer_furniture = map.createLayer('furniture');
978 map.setCollisionBetween(1, 100, true, this.layer_furniture);
979
980 // FIXME: Don't create a group here like this, it's too ugly! Now I have it here due to the display order.
981 logic.doors = game.add.group(game.world, "doors");
982
983 map.objects['objects'].forEach(function(o) { logic.createObject(o); });
984 console.log(map.objects);
985
986 cursors = game.input.keyboard.createCursorKeys();
987 console.log(logic);
988
989 }
990
991 update() {
992
993 game.physics.arcade.collide(logic.player, this.layer_walls);
994 game.physics.arcade.collide(logic.player, this.layer_furniture);
995 game.physics.arcade.collide(logic.player, logic.doors);
996 logic.update();
997
998 }
999 }
1000
1001 class GameOver extends Phaser.State {
1002
1003 create() {
1004
1005 console.log("Entered GameOver state.");
1006 game.stage.removeChildren(1);
1007 game.camera.follow(logic.player);
1008 logic.player.freeze();
1009
1010 var g = game.add.group(game.stage, "GAMEOVER");
1011 this.back_gameover = new Phaser.Graphics(game, 100, game.height / 2 - 200);
1012 this.back_gameover.beginFill(0x000000);
1013 this.back_gameover.drawRect(0, 0, game.width - 200, 400);
1014 this.back_gameover.endFill();
1015 this.back_gameover.lineStyle(3, Phaser.Color.YELLOW, 1);
1016 this.back_gameover.moveTo(10, 35);
1017 this.back_gameover.lineTo(this.back_gameover.width - 10, 35);
1018 g.add(this.back_gameover);
1019 var style = { align: 'left', fill: 'yellow', font: 'Ubuntu Mono', fontSize: 22, fontWeight: 'bold' };
1020 this.text_title = new Phaser.Text(game, this.back_gameover.x + 15, this.back_gameover.y + 6, "ABRUPT GAME OVER", style);
1021 g.add(this.text_title);
1022 style = { align: 'left', fill: 'white', font: 'Ubuntu Mono', fontSize: 18, fontWeight: 'bold' };
1023 this.text_gameover = new Phaser.Text(game, this.back_gameover.x + 35, this.back_gameover.y + 60, null, style);
1024 this.text_gameover.wordWrap = true;
1025 this.text_gameover.wordWrapWidth = this.back_gameover.width - 70;
1026 this.text_gameover.lineSpacing = -5;
1027 g.add(this.text_gameover);
1028
1029 this.text_gameover.text = this.assess();
1030
1031 }
1032
1033 assess() {
1034 if (logic.checkAllHappy()) {
1035 return "You listened to all of your colleagues' and affected them to make their day a little better. Next day, the company fired Clara, Carlos, Saiki, and you, to comply with its productivity requirements. The company's internal code demand unconditional and illimitable loyalty from employees to maximize productivity. This includes requisites for employees not to exercise human qualities or express personal needs within and outside the corporate environment. The company has determined that neither of the mentioned people complied to these guidelines, due to the amount of socialization they pursued during the work day.\n\nIronically, the layoffs turned everyone's life for the better.";
1036 }
1037 else if (logic.player.disguise) {
1038 this.text_gameover.style.fill = 'red';
1039 return "You went on your day to accomplish your tasks without caring about anyone. Where are your colleagues now? Clara, Carlos, Saiki? You don't know. But you don't even care. You have never seen them after they served your needs.\n\nNext day, the company promoted you for your effectiveness and loyalty.";
1040 }
1041 else {
1042 return "The company is concerned that you may develop other needs (like wanting to pursue hobbies, spend time with friends and family) those compete for the time you devote for your job. The company reminds you that they demand your entire being to exclusively serve the company's interests. Socializing with your colleagues outside the dedicated team building occasions may hurt your performance. To comply with the productivity requirements, the company has terminated your employment.\n\nClara, Carlos, Saiki are still loyal employees of the company. Probably you could have done something different to find a better outcome.";
1043 }
1044 }
1045
1046 }