window._ = {}; window.Game = {}; Game.sections = {}; Game.queue = []; Game.dom = $("#game_container"); Game.wordsDOM = $("#game_words"); Game.choicesDOM = $("#game_choices"); Game.canvas = $("#game_canvas"); window.SceneSetup = {}; // A big ol' singleton class that just makes it easy to create scenes. // HELPER FUNCS window.bb = function(){ publish("bb", arguments); }; window.hong = function(){ publish("hong", arguments); }; window.attack = function(damage, type){ publish("attack", ["hong", damage, type]); _["attack_"+type+"_ch"+_.CHAPTER]++; // HACK }; window.attackBB = function(damage, type){ publish("attack", ["bb", damage]); }; // Init Game.init = function(){ // HP! window.HP = new HitPoints(); // Animation! console.log("init"); Game.wordsDOM.style.top = "80px"; var animloop = function(){ Game.update(); requestAnimationFrame(animloop); }; requestAnimationFrame(animloop); }; // Parse scene markdown! Game.parseSceneMarkdown = function(md){ // Split into sections... md = md.trim(); md = "\n" + md; var sections = md.split(/\n\#\s*/); sections.shift(); sections.forEach(function(section){ var split_index = section.indexOf("\n\n"); var id = section.slice(0, split_index).toLocaleLowerCase(); var text = section.slice(split_index+2); // Split into lines text = text.trim(); var lines = text.split("\n\n"); for(var i=0; i0){ promiseNext.then(function(){ Game.executeNextLine(); }); } }; Game.clearQueue = function(){ Game.queue = []; }; Game.addToQueue = function(line){ Game.queue.push(line); } //////////////////////////////////////////////////////////////////////////////////////////////// // TEXT AND STUFF ////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// // Immediate Promise Game.immediatePromise = function(){ return new RSVP.Promise(function(resolve){ resolve(); }); }; // Move the text DOM to latest Game.FORCE_TEXT_Y = -1; Game.updateText = function(instant){ if(Game.FORCE_TEXT_Y<0){ var wordsHeight = 80 + Game.wordsDOM.getBoundingClientRect().height; var currentY = parseFloat(Game.wordsDOM.style.top) || 80; var gotoY = (wordsHeight<250) ? 0 : wordsHeight-250; gotoY = 80 - gotoY; var nextY = instant ? gotoY : currentY*0.9 + gotoY*0.1; Game.wordsDOM.style.top = (Math.round(nextY*10)/10)+"px"; }else{ Game.wordsDOM.style.top = Game.FORCE_TEXT_Y+"px"; } }; // CLEAR TEXT Game.clearText = function(){ Game.wordsDOM.innerHTML = ""; Game.updateText(true); }; Game.clearAll = function(){ Game.clearText(); Game.resetScene(); music(null); stopAllSounds(); }; window.clearText = Game.clearText; // Execute text! Just add it to text DOM. Game.TEXT_SPEED = 50; Game.CLICK_TO_ADVANCE = true; Game.FORCE_CANT_SKIP = false; Game.OVERRIDE_TEXT_SPEED = 1; Game.FORCE_TEXT_DURATION = -1; Game.WHO_IS_SPEAKING = null; // "h", "b", "n" etc... Game.CURRENT_SPEAKING_SPEED = 1; Game.FORCE_NO_VOICE = false; Game.NO_NARRATOR_SOUNDS = false; Game.executeText = function(line){ return new RSVP.Promise(function(resolve){ // Who's speaking? // b: Beebee, h: Hong, n: Narrator, n2: Narrator 2, n3: Narrator 3 var regex = /^([^\:]+)\:(.*)/ var speaker = line.match(regex)[1].trim(); var dialogue = line.match(regex)[2].trim(); // Add the bubble, with animation var div = document.createElement("div"); Game.wordsDOM.appendChild(div); Game.WHO_IS_SPEAKING = speaker; // WHO'S SPEAKING?! Game.CURRENT_SPEAKING_SPEED = Game.OVERRIDE_TEXT_SPEED; switch(speaker){ case "b": div.className = "beebee-bubble"; break; case "h": div.className = "hong-bubble"; break; case "h2": case "h3": div.className = "hong2-bubble"; break; case "a": div.className = "al-bubble"; break; case "s": div.className = "shire-bubble"; break; case "r": div.className = "hunter-bubble"; break; case "n": div.className = "narrator-bubble"; break; case "n2": // narrator 2 div.className = "narrator-bubble-2"; break; case "n3": // narrator 3 div.className = "narrator-bubble-3"; break; case "n4": // narrator 4 div.className = "narrator-bubble-4"; break; case "n5": // narrator 5 div.className = "narrator-bubble-5"; break; case "i": // Intermission div.className = "narrator-bubble-i"; break; } requestAnimationFrame(function(){ requestAnimationFrame(function(){ div.style.opacity = 1; div.style.left = 0; }); }); // Clear both var clearBoth = document.createElement("div"); clearBoth.className = "clear-both"; Game.wordsDOM.appendChild(clearBoth); // Add the text var interval = 0; var SPEED = Math.round(Game.TEXT_SPEED / Game.OVERRIDE_TEXT_SPEED); if(Game.FORCE_TEXT_DURATION>0){ SPEED = Math.round(Game.FORCE_TEXT_DURATION/dialogue.length); } // IF IT'S BEEBEE, HONG, or NARRATOR 3, or HUNTER, or AL or SHIRE if(speaker=="b" || speaker=="h" || speaker=="h2" || speaker=="h3" || speaker=="n3" || speaker=="r" || speaker=="a" || speaker=="s"){ // Put in the text, each character a DIFFERENT SPAN... var span, chr; var isItalicized = false; for(var i=0; i"+chr+"" : chr; } span.style.opacity = 0; div.appendChild(span); } // Then REVEAL letters one-by-one for(var i=0; i *Emphasize* *multiple* *words* var regex = /\*([^\*]*)\*/g; var emphasized = dialogue.match(regex) || []; for(var i=emphasized.length-1; i>=0; i--){ // backwards coz replacing // Convert var originalEm = emphasized[i] var em = originalEm; em = em.substr(1,em.length-2); // remove * var ems = em.split(" "); ems = ems.map(function(word){ return "*"+word+"*"; }); em = ems.join(" "); // Replace in main string var startIndex = dialogue.indexOf(originalEm); dialogue = dialogue.slice(0, startIndex) + em + dialogue.slice(startIndex+originalEm.length); } // Put in the text, each word a DIFFERENT SPAN var span; var dialogueWords = dialogue.split(" "); for(var i=0; i"; } // Actual emphasis reggie = /\_(.*)\_/; if(reggie.test(word)){ word = "" + word.match(reggie)[1].trim() + ""; } // Add the span span = document.createElement("span"); span.innerHTML = word+" "; span.style.opacity = 0; div.appendChild(span); } // Then REVEAL words one-by-one for(var i=0; i" + results[1] + "" + choiceText.slice(endOfMatch); } var div = document.createElement("div"); div.innerHTML = choiceText; div.setAttribute("speaker", Game.OVERRIDE_CHOICE_SPEAKER ? Game.OVERRIDE_CHOICE_SPEAKER : "b"); div.onclick = function(event){ // Any pre-choice code? if(preChoiceCodeIfAny) Game.executeCode(preChoiceCodeIfAny); // Override line... ONCE if(!Game.OVERRIDE_CHOICE_LINE){ if(Game.OVERRIDE_CHOICE_SPEAKER){ Game.addToQueue(Game.OVERRIDE_CHOICE_SPEAKER+": "+originalChoiceText); }else{ Game.addToQueue("b: "+originalChoiceText); } } Game.OVERRIDE_CHOICE_SPEAKER = null; Game.OVERRIDE_CHOICE_LINE = false; // Play sound sfx("ui_click"); // Goto that choice, now! Game.goto(choiceID); // STOP THE PROP event.stopPropagation(); }; div.onmouseover = function(){ sfx("ui_hover"); }; // Add choice, animated! div.style.top = "150px"; Game.choicesDOM.appendChild(div); setTimeout(function(){ div.style.top = "0px"; sfx("ui_show_choice", {volume:0.4}); },10); // Or... FORCE if(Game.HACK_MAKE_THE_LINE_BIG){ div.style.fontSize = "30px"; }else{ // If it's too big, shrink font size setTimeout(function(){ var choiceHeight = div.getBoundingClientRect().height; if(choiceHeight>40) div.style.fontSize = "18px"; // And if still too much??? setTimeout(function(){ var choiceHeight = div.getBoundingClientRect().height; if(choiceHeight>40) div.style.fontSize = "16px"; // And if still too much??? setTimeout(function(){ var choiceHeight = div.getBoundingClientRect().height; if(choiceHeight>40) div.style.fontSize = "14px"; // And if still too much??? setTimeout(function(){ var choiceHeight = div.getBoundingClientRect().height; if(choiceHeight>40) div.style.fontSize = "12px"; },1); },1); },1); },1); } Game.HACK_MAKE_THE_LINE_BIG = false; // Wait a bit before adding new line return new RSVP.Promise(function(resolve){ Game.setTimeout(resolve, 100); }); } // Execute code! Game.executeCode = function(line){ var code = line.match(/\`+([^\`]*)\`+/)[1].trim(); try{ eval(code); }catch(e){ console.log(e); } // Return immediate promise return Game.immediatePromise(); } // Execute wait! Just wait. Game.executeWait = function(line){ // Get integer from (...NN) var waitTime = parseInt(line.match(/^\(\.\.\.(\d+)\)/)[1].trim()); // Unless it's click to advance, then IGNORE ALL WAITS if(!Game.FORCE_CANT_SKIP){ // Specific wait-time, don't skip? var waitTimeString = waitTime+""; var lastDigit = waitTimeString[waitTimeString.length-1]; var cantSkip = (lastDigit!="0"); // CAN'T SKIP. if(!cantSkip && Game.CLICK_TO_ADVANCE && waitTime<=999){ // hack: unless the wait is long. waitTime = 0; } } // Delayed promise return new RSVP.Promise(function(resolve){ Game.setTimeout(resolve, waitTime); }); }; // Execute goto! Just goto. Game.executeGoto = function(line){ var gotoID = line.match(/^\(\#(.*)\)/)[1].trim().toLocaleLowerCase(); Game.goto(gotoID); } // Determine line type... text, choice, or code? Game.getLineType = function(line){ // Is it a choice? var isChoice = /\[.*\]\(\#.*\)/.test(line); if(isChoice) return "choice"; // Is it a goto? var isGoto = /^\(\#(.*)\)/.test(line); if(isGoto) return "goto"; // Is it code? var isCode = /^\`/.test(line); if(isCode) return "code"; // Is it a wait? var isWait = /^\(\.\.\.\d+\)/.test(line); if(isWait) return "wait"; // Otherwise, it's text. return "text"; }; // Parse all the handlebars... Game.parseLine = function(line){ // Get rid of newlines line = line.replace(/\n/gi,""); // Get the IFs, if any var lookForIfs = true; while(lookForIfs){ lookForIfs = false; // Look for an IF! var regex = /\{\{if[^\/]*\/if\}\}/ig; // the reason it's inside here is to reset .exec var regexResult = regex.exec(line); if(regexResult){ // The result... var fullConditional = regexResult[0]; var startsAtIndex = regexResult.index; var endsAtIndex = startsAtIndex + fullConditional.length; // Extract the condition var condition = fullConditional.match(/\{\{if\s+([^\{\}]*)\}\}/)[1]; // Extract the inside text var insideText = fullConditional.match(/\}\}([^\{\}]*)\{\{/)[1].trim()+" "; // Eval condition! var conditionIsTrue = false; try{ conditionIsTrue = eval(condition); }catch(e){ console.log(e); } // Edit the line var insert = conditionIsTrue ? insideText : ""; line = line.slice(0,startsAtIndex) + insert + line.slice(endsAtIndex); // Keep searching... lookForIfs = true; } } // Evaluate {{expressions}}, if any var lookForExpressions = true; while(lookForExpressions){ lookForExpressions = false; // Look for an IF! //debugger; var regex = /\{\{[^\}]*\}\}/ig; // the reason it's inside here is to reset .exec var regexResult = regex.exec(line); if(regexResult){ // The result... var fullExpression = regexResult[0]; var startsAtIndex = regexResult.index; var endsAtIndex = startsAtIndex + fullExpression.length; // Extract the expression var expression = fullExpression.match(/\{\{([^\}]*)\}\}/)[1]; // Eval condition! var evaluated = ""; try{ evaluated = eval(expression); }catch(e){ console.log(e); } // Edit the line line = line.slice(0,startsAtIndex) + evaluated + line.slice(endsAtIndex); // Keep searching... lookForExpressions = true; } } // Return line! return line; }; //////////////////////////////////////////////////////////////////////////////////////////////// // WHERE STUFF WILL BE DRAWN /////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// Game.canvas.width = 360 * 2; Game.canvas.height = 600 * 2; //450 * 2; Game.canvas.style.width = Game.canvas.width/2 + "px"; Game.canvas.style.height = Game.canvas.height/2 + "px"; Game.context = Game.canvas.getContext("2d"); // A blank scene Game.scene = null; Game.resetScene = function(){ // Kill all of previous scene if(Game.scene){ Game.scene.children.forEach(function(child){ if(child.kill) child.kill(); }); if(Game.scene.kill) Game.scene.kill(); } // New scene! Game.scene = {}; Game.scene.children = []; }; Game.resetScene(); // Update & draw all the kids! Game.updateCanvas = function(){ // For retina var ctx = Game.context; ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); ctx.save(); ctx.scale(2,2); // Update/Draw all kids Game.scene.children.forEach(function(child){ if(child.update) child.update(); }); Game.scene.children.forEach(function(child){ child.draw(ctx); }); // Restore ctx.restore(); // Draw HP HP.draw(); }; // HACK: PREVENT ACCIDENTALLY TABBING & BREAKING UI window.addEventListener("keydown", function(e){ if(e.keyCode==9){ e.preventDefault(); e.stopPropagation(); } });