diff --git a/main.js b/main.js index 6b53181..fc47fa1 100644 --- a/main.js +++ b/main.js @@ -1,1187 +1,1187 @@ -// Copyright (c)2002-2010 Eric Fredricksen all rights reserved - -var game = {}; -var lasttick, timerid; - -function timeGetTime() { - return new Date().getTime(); -} - -function StartTimer() { - if (!timerid) { - lasttick = timeGetTime(); - timerid = setTimeout(Timer1Timer, 100); - } -} - -function StopTimer() { - clearTimeout(timerid); - timerid = null; -} - -function Q(s) { - game.queue.push(s); - Dequeue(); -} - -function TaskDone() { - return TaskBar.done(); -} - -function Odds(chance, outof) { - return Random(outof) < chance; -} - -function RandSign() { - return Random(2) * 2 - 1; -} - -function RandomLow(below) { - return Min(Random(below), Random(below)); -} - -function PickLow(s) { - return s[RandomLow(s.length)]; -} - -function Copy(s, b, l) { - return s.substr(b-1, l); -} - -function Length(s) { - return s.length; -} - -function Starts(s, pre) { - return 0 === s.indexOf(pre); -} - -function Ends(s, e) { - return Copy(s, 1+Length(s)-Length(e), Length(e)) == e; -} - -function Plural(s) { - if (Ends(s,'y')) - return Copy(s,1,Length(s)-1) + 'ies'; - else if (Ends(s,'us')) - return Copy(s,1,Length(s)-2) + 'i'; - else if (Ends(s,'ch') || Ends(s,'x') || Ends(s,'s') || Ends(s, 'sh')) - return s + 'es'; - else if (Ends(s,'f')) - return Copy(s,1,Length(s)-1) + 'ves'; - else if (Ends(s,'man') || Ends(s,'Man')) - return Copy(s,1,Length(s)-2) + 'en'; - else return s + 's'; -} - -function Split(s, field, separator) { - return s.split(separator || "|")[field]; -} - -function Indefinite(s, qty) { - if (qty == 1) { - if (Pos(s.charAt(0), 'AEIOUÜaeiouü') > 0) - return 'an ' + s; - else - return 'a ' + s; - } else { - return IntToStr(qty) + ' ' + Plural(s); - } -} - -function Definite(s, qty) { - if (qty > 1) - s = Plural(s); - return 'the ' + s; -} - -function prefix(a, m, s, sep) { - if (sep == undefined) sep = ' '; - m = Abs(m); - if (m < 1 || m > a.length) return s; // In case of screwups - return a[m-1] + sep + s; -} - -function Sick(m, s) { - m = 6 - Abs(m); - return prefix(['dead','comatose','crippled','sick','undernourished'], m, s); -} - - -function Young(m, s) { - m = 6 - Abs(m); - return prefix(['foetal','baby','preadolescent','teenage','underage'], m, s); -} - - -function Big(m, s) { - return prefix(['greater','massive','enormous','giant','titanic'], m, s); -} - -function Special(m, s) { - if (Pos(' ', s) > 0) - return prefix(['veteran','cursed','warrior','undead','demon'], m, s); - else - return prefix(['Battle-','cursed ','Were-','undead ','demon '], m, s, ''); -} - -function InterplotCinematic() { - switch (Random(3)) { - case 0: - Q('task|1|Exhausted, you arrive at a friendly oasis in a hostile land'); - Q('task|2|You greet old friends and meet new allies'); - Q('task|2|You are privy to a council of powerful do-gooders'); - Q('task|1|There is much to be done. You are chosen!'); - break; - case 1: - Q('task|1|Your quarry is in sight, but a mighty enemy bars your path!'); - var nemesis = NamedMonster(GetI(Traits,'Level')+3); - Q('task|4|A desperate struggle commences with ' + nemesis); - var s = Random(3); - for (var i = 1; i <= Random(1 + game.act + 1); ++i) { - s += 1 + Random(2); - switch (s % 3) { - case 0: Q('task|2|Locked in grim combat with ' + nemesis); break; - case 1: Q('task|2|' + nemesis + ' seems to have the upper hand'); break; - case 2: Q('task|2|You seem to gain the advantage over ' + nemesis); break; - } - } - Q('task|3|Victory! ' + nemesis + ' is slain! Exhausted, you lose conciousness'); - Q('task|2|You awake in a friendly place, but the road awaits'); - break; - case 2: - var nemesis2 = ImpressiveGuy(); - Q("task|2|Oh sweet relief! You've reached the protection of the good " + nemesis2); - Q('task|3|There is rejoicing, and an unnerving encouter with ' + nemesis2 + ' in private'); - Q('task|2|You forget your ' + BoringItem() + ' and go back to get it'); - Q("task|2|What's this!? You overhear something shocking!"); - Q('task|2|Could ' + nemesis2 + ' be a dirty double-dealer?'); - Q('task|3|Who can possibly be trusted with this news!? ... Oh yes, of course'); - break; - } - Q('plot|1|Loading'); -} - - -function StrToInt(s) { - return parseInt(s, 10); -} - -function IntToStr(i) { - return i + ""; -} - -function NamedMonster(level) { - var lev = 0; - var result = ''; - for (var i = 0; i < 5; ++i) { - var m = Pick(K.Monsters); - if (!result || (Abs(level-StrToInt(Split(m,1))) < Abs(level-lev))) { - result = Split(m,0); - lev = StrToInt(Split(m,1)); - } - } - return GenerateName() + ' the ' + result; -} - -function ImpressiveGuy() { - return Pick(K.ImpressiveTitles) + - (Random(2) ? ' of the ' + Pick(K.Races) : ' of ' + GenerateName()); -} - -function MonsterTask(level) { - var definite = false; - for (var i = level; i >= 1; --i) { - if (Odds(2,5)) - level += RandSign(); - } - if (level < 1) level = 1; - // level = level of puissance of opponent(s) we'll return - - var monster, lev; - if (Odds(1,25)) { - // Use an NPC every once in a while - monster = ' ' + Split(Pick(K.Races), 0); - if (Odds(1,2)) { - monster = 'passing' + monster + ' ' + Split(Pick(K.Klasses), 0); - } else { - monster = PickLow(K.Titles) + ' ' + GenerateName() + ' the' + monster; - definite = true; - } - lev = level; - monster = monster + '|' + IntToStr(level) + '|*'; - } else if (game.questmonster && Odds(1,4)) { - // Use the quest monster - monster = K.Monsters[game.questmonsterindex]; - lev = StrToInt(Split(monster,1)); - } else { - // Pick the monster out of so many random ones closest to the level we want - monster = Pick(K.Monsters); - lev = StrToInt(Split(monster,1)); - for (var ii = 0; ii < 5; ++ii) { - var m1 = Pick(K.Monsters); - if (Abs(level-StrToInt(Split(m1,1))) < Abs(level-lev)) { - monster = m1; - lev = StrToInt(Split(monster,1)); - } - } - } - - var result = Split(monster,0); - game.task = 'kill|' + monster; - - var qty = 1; - if (level-lev > 10) { - // lev is too low. multiply... - qty = Math.floor((level + Random(Max(lev,1))) / Max(lev,1)); - if (qty < 1) qty = 1; - level = Math.floor(level / qty); - } - - if ((level - lev) <= -10) { - result = 'imaginary ' + result; - } else if ((level-lev) < -5) { - i = 10+(level-lev); - i = 5-Random(i+1); - result = Sick(i,Young((lev-level)-i,result)); - } else if (((level-lev) < 0) && (Random(2) == 1)) { - result = Sick(level-lev,result); - } else if (((level-lev) < 0)) { - result = Young(level-lev,result); - } else if ((level-lev) >= 10) { - result = 'messianic ' + result; - } else if ((level-lev) > 5) { - i = 10-(level-lev); - i = 5-Random(i+1); - result = Big(i,Special((level-lev)-i,result)); - } else if (((level-lev) > 0) && (Random(2) == 1)) { - result = Big(level-lev,result); - } else if (((level-lev) > 0)) { - result = Special(level-lev,result); - } - - lev = level; - level = lev * qty; - - if (!definite) result = Indefinite(result, qty); - return { 'description': result, 'level': level }; -} - -function LowerCase(s) { - return s.toLowerCase(); -} - -function ProperCase(s) { - return Copy(s,1,1).toUpperCase() + Copy(s,2,10000); -} - -function EquipPrice() { - return 5 * GetI(Traits,'Level') * GetI(Traits,'Level') + - 10 * GetI(Traits,'Level') + - 20; -} - -function Dequeue() { - while (TaskDone()) { - if (Split(game.task,0) == 'kill') { - if (Split(game.task,3) == '*') { - WinItem(); - } else if (Split(game.task,3)) { - Add(Inventory,LowerCase(Split(game.task,1) + ' ' + - ProperCase(Split(game.task,3))),1); - } - } else if (game.task == 'buying') { - // buy some equipment - Add(Inventory,'Gold',-EquipPrice()); - WinEquip(); - } else if ((game.task == 'market') || (game.task == 'sell')) { - if (game.task == 'sell') { - var amt = GetI(Inventory, 1) * GetI(Traits,'Level'); - if (Pos(' of ', Inventory.label(1)) > 0) - amt *= (1+RandomLow(10)) * (1+RandomLow(GetI(Traits,'Level'))); - Inventory.remove1(); - Add(Inventory, 'Gold', amt); - } - if (Inventory.length() > 1) { - Inventory.scrollToTop(); - Task('Selling ' + Indefinite(Inventory.label(1), GetI(Inventory,1)), - 1 * 1000); - game.task = 'sell'; - break; - } - } - - var old = game.task; - game.task = ''; - if (game.queue.length > 0) { - var a = Split(game.queue[0],0); - var n = StrToInt(Split(game.queue[0],1)); - var s = Split(game.queue[0],2); - if (a == 'task' || a == 'plot') { - game.queue.shift(); - if (a == 'plot') { - CompleteAct(); - s = 'Loading ' + game.bestplot; - } - Task(s, n * 1000); - } else { - throw 'bah!' + a; - } - } else if (EncumBar.done()) { - Task('Heading to market to sell loot',4 * 1000); - game.task = 'market'; - } else if ((Pos('kill|',old) <= 0) && (old != 'heading')) { - if (GetI(Inventory, 'Gold') > EquipPrice()) { - Task('Negotiating purchase of better equipment', 5 * 1000); - game.task = 'buying'; - } else { - Task('Heading to the killing fields', 4 * 1000); - game.task = 'heading'; - } - } else { - var nn = GetI(Traits, 'Level'); - var t = MonsterTask(nn); - var InventoryLabelAlsoGameStyleTag = 3; - nn = Math.floor((2 * InventoryLabelAlsoGameStyleTag * t.level * 1000) / nn); - Task('Executing ' + t.description, nn); - } - } -} - - -function Put(list, key, value) { - if (typeof key === typeof 1) - key = list.label(key); - - if (list.fixedkeys) { - game[list.id][key] = value; - } else { - var i = 0; - for (; i < game[list.id].length; ++i) { - if (game[list.id][i][0] === key) { - game[list.id][i][1] = value; - break; - } - } - if (i == game[list.id].length) - game[list.id].push([key,value]); - } - - list.PutUI(key, value); - - if (key === 'STR') - EncumBar.reset(10 + value, EncumBar.Position()); - - if (list === Inventory) { - var cubits = 0; - $.each(game.Inventory.slice(1), function (index, item) { - cubits += StrToInt(item[1]); - }); - EncumBar.reposition(cubits); - } -} - - -function ProgressBar(id, tmpl) { - this.id = id; - this.bar = $("#"+ id + " > .bar"); - this.tmpl = tmpl; - - this.Max = function () { return game[this.id].max; }; - this.Position = function () { return game[this.id].position; }; - - this.reset = function (newmax, newposition) { - game[this.id].max = newmax; - this.reposition(newposition || 0); - }; - - this.reposition = function (newpos) { - game[this.id].position = Min(newpos, this.Max()); - - // Recompute hint - game[this.id].percent = (100 * this.Position()).div(this.Max()); - game[this.id].remaining = Math.floor(this.Max() - this.Position()); - game[this.id].time = RoughTime(this.Max() - this.Position()); - game[this.id].hint = template(this.tmpl, game[this.id]); - - // Update UI - if (this.bar) { - var p = this.Max() ? 100 * this.Position() / this.Max() : 0; - this.bar.css("width", p + "%"); - this.bar.parent().find(".hint").text(game[this.id].hint); - } - }; - - this.increment = function (inc) { - this.reposition(this.Position() + inc); - }; - - this.done = function () { - return this.Position() >= this.Max(); - }; - - this.load = function (game) { - this.reposition(this.Position()); - }; -} - - - -function Key(tr) { - return $(tr).children().first().text(); -} - -function Value(tr) { - return $(tr).children().last().text(); -} - - - -function ListBox(id, columns, fixedkeys) { - this.id = id; - this.box = $("tbody#_, #_ tbody".replace(/_/g, id)); - this.columns = columns; - this.fixedkeys = fixedkeys; - - this.AddUI = function (caption) { - if (!this.box) return; - var tr = $(" " + - caption + ""); - tr.appendTo(this.box); - tr.each(function () {this.scrollIntoView();}); - return tr; - }; - - this.ClearSelection = function () { - if (this.box) - this.box.find("tr").removeClass("selected"); - }; - - this.PutUI = function (key, value) { - if (!this.box) return; - var item = this.rows().filter(function (index) { - return Key(this) === key; - }); - if (!item.length) { - item = $("" + key + ""); - this.box.append(item); - } - - item.children().last().text(value); - item.addClass("selected"); - item.each(function () {this.scrollIntoView();}); - }; - - this.scrollToTop = function () { - if (this.box) - this.box.parents(".scroll").scrollTop(0); - }; - - this.rows = function () { - return this.box.find("tr").has("td"); - }; - - this.CheckAll = function (butlast) { - if (this.box) { - if (butlast) - this.rows().find("input:checkbox").not(':last').attr("checked","true"); - else - this.rows().find("input:checkbox").attr("checked","true"); - } - }; - - this.length = function () { - return (this.fixedkeys || game[this.id]).length; - }; - - this.remove0 = function (n) { - if (game[this.id]) - game[this.id].shift(); - if (this.box) - this.box.find("tr").first().remove(); - }; - - this.remove1 = function (n) { - var t = game[this.id].shift(); - game[this.id].shift(); - game[this.id].unshift(t); - if (this.box) - this.box.find("tr").eq(1).remove(); - }; - - - this.load = function (game) { - var that = this; - var dict = game[this.id]; - if (this.fixedkeys) { - $.each(this.fixedkeys, function (index, key) { - that.PutUI(key, dict[key]); - }); - } else { - $.each(dict, function (index, row) { - if (that.columns == 2) - that.PutUI(row[0], row[1]); - else - that.AddUI(row); - }); - } - }; - - - this.label = function (n) { - return this.fixedkeys ? this.fixedkeys[n] : game[this.id][n][0]; - }; -} - - -var ExpBar, PlotBar, TaskBar, QuestBar, EncumBar; -var Traits,Stats,Spells,Equips,Inventory,Plots,Quests; -var Kill; -var AllBars, AllLists; - - -function StrToIntDef(s, def) { - var result = parseInt(s, 10); - return isNaN(result) ? def : result; -} - - -if (document) - $(document).ready(FormCreate); - - -function WinSpell() { - AddR(Spells, K.Spells[RandomLow(Min(GetI(Stats,'WIS')+GetI(Traits,'Level'), - K.Spells.length))], 1); -} - -function LPick(list, goal) { - var result = Pick(list); - for (var i = 1; i <= 5; ++i) { - var best = StrToInt(Split(result, 1)); - var s = Pick(list); - var b1 = StrToInt(Split(s,1)); - if (Abs(goal-best) > Abs(goal-b1)) - result = s; - } - return result; -} - -function Abs(x) { - if (x < 0) return -x; else return x; -} - -function WinEquip() { - var posn = Random(Equips.length()); - - if (!posn) { - stuff = K.Weapons; - better = K.OffenseAttrib; - worse = K.OffenseBad; - } else { - better = K.DefenseAttrib; - worse = K.DefenseBad; - stuff = (posn == 1) ? K.Shields: K.Armors; - } - var name = LPick(stuff, GetI(Traits,'Level')); - var qual = StrToInt(Split(name,1)); - name = Split(name,0); - var plus = GetI(Traits,'Level') - qual; - if (plus < 0) better = worse; - var count = 0; - while (count < 2 && plus) { - var modifier = Pick(better); - qual = StrToInt(Split(modifier, 1)); - modifier = Split(modifier, 0); - if (Pos(modifier, name) > 0) break; // no repeats - if (Abs(plus) < Abs(qual)) break; // too much - name = modifier + ' ' + name; - plus -= qual; - ++count; - } - if (plus) name = plus + ' ' + name; - if (plus > 0) name = '+' + name; - - Put(Equips, posn, name); - game.bestequip = name; - if (posn > 1) game.bestequip += ' ' + Equips.label(posn); -} - - -function Square(x) { return x * x; } - -function WinStat() { - var i; - if (Odds(1,2)) { - i = Pick(K.Stats); - } else { - // Favor the best stat so it will tend to clump - var t = 0; - $.each(K.Stats, function (index, key) { - t += Square(GetI(Stats, key)); - }); - t = Random(t); - $.each(K.Stats, function (index, key) { - i = key; - t -= Square(GetI(Stats, key)); - if (t < 0) return false; - }); - } - Add(Stats, i, 1); -} - -function SpecialItem() { - return InterestingItem() + ' of ' + Pick(K.ItemOfs); -} - -function InterestingItem() { - return Pick(K.ItemAttrib) + ' ' + Pick(K.Specials); -} - -function BoringItem() { - return Pick(K.BoringItems); -} - -function WinItem() { - Add(Inventory, SpecialItem(), 1); -} - -function CompleteQuest() { - QuestBar.reset(50 + RandomLow(1000)); - if (Quests.length()) { - Log('Quest completed: ' + game.bestquest); - Quests.CheckAll(); - [WinSpell,WinEquip,WinStat,WinItem][Random(4)](); - } - while (Quests.length() > 99) - Quests.remove0(); - - game.questmonster = ''; - var caption; - switch (Random(5)) { - case 0: - var level = GetI(Traits,'Level'); - var lev = 0; - for (var i = 1; i <= 4; ++i) { - var montag = Random(K.Monsters.length); - var m = K.Monsters[montag]; - var l = StrToInt(Split(m,1)); - if (i == 1 || Abs(l - level) < Abs(lev - level)) { - lev = l; - game.questmonster = m; - game.questmonsterindex = montag; - } - } - caption = 'Exterminate ' + Definite(Split(game.questmonster,0),2); - break; - case 1: - caption = 'Seek ' + Definite(InterestingItem(), 1); - break; - case 2: - caption = 'Deliver this ' + BoringItem(); - break; - case 3: - caption = 'Fetch me ' + Indefinite(BoringItem(), 1); - break; - case 4: - var mlev = 0; - level = GetI(Traits,'Level'); - for (var ii = 1; ii <= 2; ++ii) { - montag = Random(K.Monsters.length); - m = K.Monsters[montag]; - l = StrToInt(Split(m,1)); - if ((ii == 1) || (Abs(l - level) < Abs(mlev - level))) { - mlev = l; - game.questmonster = m; - } - } - caption = 'Placate ' + Definite(Split(game.questmonster,0),2); - game.questmonster = ''; // We're trying to placate them, after all - break; - } - if (!game.Quests) game.Quests = []; - while (game.Quests.length > 99) game.Quests.shift(); - game.Quests.push(caption); - game.bestquest = caption; - Quests.AddUI(caption); - - - Log('Commencing quest: ' + caption); - - SaveGame(); -} - -function toRoman(n) { - if (!n) return "N"; - var s = ""; - function _rome(dn,ds) { - if (n >= dn) { - n -= dn; - s += ds; - return true; - } else return false; - } - if (n < 0) { - s = "-"; - n = -n; - } - while (_rome(1000,"M")) {0;} - _rome(900,"CM"); - _rome(500,"D"); - _rome(400,"CD"); - while (_rome(100,"C")) {0;} - _rome(90,"XC"); - _rome(50,"L"); - _rome(40,"XL"); - while (_rome(10,"X")) {0;} - _rome(9,"IX"); - _rome(5,"V"); - _rome(4,"IV"); - while (_rome(1,"I")) {0;} - return s; -} - -function toArabic(s) { - n = 0; - s = s.toUpperCase(); - function _arab(ds,dn) { - if (!Starts(s, ds)) return false; - s = s.substr(ds.length); - n += dn; - return true; - } - while (_arab("M",1000)) {0;} - _arab("CM",900); - _arab("D",500); - _arab("CD",400); - while (_arab("C",100)) {0;} - _arab("XC",90); - _arab("L",50); - _arab("XL",40); - while (_arab("X",10)) {0;} - _arab("IX",9); - _arab("V",5); - _arab("IV",4); - while (_arab("I",1)) {0;} - return n; -} - -function CompleteAct() { - Plots.CheckAll(); - game.act += 1; - PlotBar.reset(60 * 60 * (1 + 5 * game.act)); // 1 hr + 5/act - Plots.AddUI((game.bestplot = 'Act ' + toRoman(game.act))); - - if (game.act > 1) { - WinItem(); - WinEquip(); - } - - Brag('act'); -} - - -function Log(line) { - if (game.log) - game.log[+new Date()] = line; - // TODO: and now what? -} - -function Task(caption, msec) { - game.kill = caption + "..."; - if (Kill) - Kill.text(game.kill); - Log(game.kill); - TaskBar.reset(msec); -} - -function Add(list, key, value) { - Put(list, key, value + GetI(list,key)); - - /*$IFDEF LOGGING*/ - if (!value) return; - var line = (value > 0) ? "Gained" : "Lost"; - if (key == 'Gold') { - key = "gold piece"; - line = (value > 0) ? "Got paid" : "Spent"; - } - if (value < 0) value = -value; - line = line + ' ' + Indefinite(key, value); - Log(line); - /*$ENDIF*/ -} - -function AddR(list, key, value) { - Put(list, key, toRoman(value + toArabic(Get(list,key)))); -} - -function Get(list, key) { - if (list.fixedkeys) { - if (typeof key === typeof 1) - key = list.fixedkeys[key]; - return game[list.id][key]; - } else if (typeof key === typeof 1) { - if (key < game[list.id].length) - return game[list.id][key][1]; - else - return ""; - } else { - for (var i = 0; i < game[list.id].length; ++i) { - if (game[list.id][i][0] === key) - return game[list.id][i][1]; - } - return ""; - } -} - -function GetI(list, key) { - return StrToIntDef(Get(list,key), 0); -} - -function Min(a,b) { - return a < b ? a : b; -} - -function Max(a,b) { - return a > b ? a : b; -} - -function LevelUp() { - Add(Traits,'Level',1); - Add(Stats,'HP Max', GetI(Stats,'CON').div(3) + 1 + Random(4)); - Add(Stats,'MP Max', GetI(Stats,'INT').div(3) + 1 + Random(4)); - WinStat(); - WinStat(); - WinSpell(); - ExpBar.reset(LevelUpTime(GetI(Traits,'Level'))); - Brag('level'); -} - -function ClearAllSelections() { - $.each(AllLists, function () {this.ClearSelection();}); -} - -function RoughTime(s) { - if (s < 120) return s + ' seconds'; - else if (s < 60 * 120) return s.div(60) + ' minutes'; - else if (s < 60 * 60 * 48) return s.div(3600) + ' hours'; - else if (s < 60 * 60 * 24 * 60) return s.div(3600 * 24) + ' days'; - else if (s < 60 * 60 * 24 * 30 * 24) return s.div(3600 * 24 * 30) +" months"; - else return s.div(3600 * 24 * 30 * 12) + " years"; - -} - -function Pos(needle, haystack) { - return haystack.indexOf(needle) + 1; -} - -var dealing = false; - -function Timer1Timer() { - timerid = null; // Event has fired - if (TaskBar.done()) { - game.tasks += 1; - game.elapsed += TaskBar.Max().div(1000); - - ClearAllSelections(); - - if (game.kill == 'Loading....') - TaskBar.reset(0); // Not sure if this is still the ticket - - // gain XP / level up - var gain = Pos('kill|', game.task) == 1; - if (gain) { - if (ExpBar.done()) - LevelUp(); - else - ExpBar.increment(TaskBar.Max() / 1000); - } - - // advance quest - if (gain && game.act >= 1) { - if (QuestBar.done() || !Quests.length()) { - CompleteQuest(); - } else { - QuestBar.increment(TaskBar.Max() / 1000); - } - } - - // advance plot - if (gain || !game.act) { - if (PlotBar.done()) - InterplotCinematic(); - else - PlotBar.increment(TaskBar.Max() / 1000); - } - - Dequeue(); - } else { - var elapsed = timeGetTime() - lasttick; - if (elapsed > 100) elapsed = 100; - if (elapsed < 0) elapsed = 0; - TaskBar.increment(elapsed); - } - - StartTimer(); -} - -function FormCreate() { - ExpBar = new ProgressBar("ExpBar", "$remaining XP needed for next level"); - EncumBar = new ProgressBar("EncumBar", "$position/$max cubits"); - PlotBar = new ProgressBar("PlotBar", "$time remaining"); - QuestBar = new ProgressBar("QuestBar", "$percent% complete"); - TaskBar = new ProgressBar("TaskBar", "$percent%"); - - AllBars = [ExpBar,PlotBar,TaskBar,QuestBar,EncumBar]; - - Traits = new ListBox("Traits", 2, K.Traits); - Stats = new ListBox("Stats", 2, K.Stats); - Spells = new ListBox("Spells", 2); - Equips = new ListBox("Equips", 2, K.Equips); - Inventory = new ListBox("Inventory", 2); - Plots = new ListBox("Plots", 1); - Quests = new ListBox("Quests", 1); - - Plots.load = function (sheet) { - for (var i = Max(0, game.act-99); i <= game.act; ++i) - this.AddUI(i ? 'Act ' + toRoman(i) : "Prologue"); - - }; - - AllLists = [Traits,Stats,Spells,Equips,Inventory,Plots,Quests]; - - if (document) { - Kill = $("#Kill"); - - $("#quit").click(quit); - - $(document).keypress(FormKeyDown); - - $(document).bind('beforeunload', function () { - if (!storage) - return "Are you sure you want to quit? All your progress will be lost!"; - }); - - $(window).unload(function (event) { - StopTimer(); - SaveGame(); - if (storage.async) { - // Have to give SQL transaction a chance to complete - if (window.showModalDialog) - pause(100); - - // Just accept some data loss - alert is too ugly. Maybe increase save - // frequency. - // else alert("Game saved"); - } - }); - - if (iOS) $("body").addClass("iOS"); - } - - var name = unescape(window.location.href.split('#')[1]); - storage.loadSheet(name, LoadGame); -} - - - -function pause(msec) { - window.showModalDialog("javascript:document.writeln ('')", - null, - "dialogWidth:0;dialogHeight:0;dialogHide:yes;unadorned:yes;"+ - "status:no;scroll:no;center:no;dialogTop:-10000;dialogLeft:-10000"); -} - -function quit() { - $(window).unbind('unload'); - SaveGame(function () { window.location.href = "roster.html"; }); -} - - -function HotOrNot() { - // Figure out which spell is best - if (Spells.length()) { - var flat = 1; // Flattening constant - var best = 0, i; - for (i = 1; i < Spells.length(); ++i) { - if ((i+flat) * toArabic(Get(Spells,i)) > - (best+flat) * toArabic(Get(Spells,best))) - best = i; - } - game.bestspell = Spells.label(best) + ' ' + Get(Spells, best); - } else { - game.bestspell = ''; - } - - /// And which stat is best? - best = 0; - for (i = 1; i <= 5; ++i) { - if (GetI(Stats,i) > GetI(Stats,best)) - best = i; - } - game.beststat = Stats.label(best) + ' ' + GetI(Stats, best); -} - - -function SaveGame(callback) { - Log('Saving game: ' + GameSaveName()); - HotOrNot(); - game.date = ''+new Date(); - game.stamp = +new Date(); - game.seed = randseed(); - storage.addToRoster(game, callback); -} - -function LoadGame(sheet) { - if (!sheet) { - alert("Error loading game"); - window.location.href = "roster.html"; - return; - } - - game = sheet; - - /* - if (!window.localStorage) { - // Cookies can't hold a whole game save - storage.removeItem("roster"); - storage = null; - } -*/ - - if (document) { - var title = "Progress Quest - " + GameSaveName(); - $("#title").text(title); - if (iOS) title = GameSaveName(); - document.title = title; - } - - - randseed(game.seed); - $.each(AllBars.concat(AllLists), function (i, e) { e.load(game); }); - if (Kill) - Kill.text(game.kill); - ClearAllSelections(); - $.each([Plots,Quests], function () { - this.CheckAll(true); - }); - Log('Loaded game: ' + game.Traits.Name); - if (!game.elapsed) - Brag('start'); - StartTimer(); -} - -function GameSaveName() { - if (!game.saveName) { - game.saveName = Get(Traits, 'Name'); - if (game.realm) - game.saveName += ' [' + game.realm + ']'; - } - return game.saveName; -} - - -function InputBox(message, def) { - var i = prompt(message, def || ''); - return (i !== null) ? i : def; -} - -function ToDna(s) { - s = s + ""; - var code = { - '0': "AT", - '1': "AG", - '2': "AC", - '3': "TA", - '4': "TG", - '5': "TC", - '6': "GA", - '7': "GT", - '8': "GC", - '9': "CA", - ',': "CT", - '.': "CG" - }; - var r = ""; - for (var i = 0; i < s.length; ++i) { - r += code[s[i]]; - if (i && (i % 4) == 0) r += " "; - } - return r; -} - -function FormKeyDown(e) { - var key = String.fromCharCode(e.which); - - if (key === 'd') { - alert("Your character's genome is " + ToDna(game.dna + "")); - } - - if (game.isonline) { - if (key === 'b') { - Brag('brag'); - //Navigate(GetHostAddr() + 'name=' + UrlEncode(Get(Traits,'Name'))); - } - - if (key === 'g') { - game.guild = InputBox('Choose a guild.\r\rMake sure you undestand the guild rules before you join one. To learn more about guilds, visit http://progressquest.com/guilds.php', game.guild); - Brag("guild"); - } - - if (key === 'm') { - game.motto = InputBox('Declare your motto!', game.motto); - Brag('motto'); - } - } - - if (key === 'p') { - if (timerid) { - $('#paused').css('display', 'block'); - StopTimer(); - } else { - $('#paused').css('display', ''); - StartTimer(); - } - } - - if (key === 'q') { - quit(); - } - - if (key === 's') { - SaveGame(); - alert('Saved (' + JSON.stringify(game).length + ' bytes).'); - } - - /* - if (key === 't') { - TaskBar.reposition(TaskBar.Max()); - } - */ -} - -function Navigate(url) { - window.open(url); -} - -function LFSR(pt, salt) { - var result = salt; - for (var k = 1; k <= Length(pt); ++k) - result = Ord(pt[k]) ^ (result << 1) ^ (1 && ((result >> 31) ^ (result >> 5))); - for (var kk = 1; kk <= 10; ++kk) - result = (result << 1) ^ (1 && ((result >> 31) ^ (result >> 5))); -} - - -function Brag(trigger) { - SaveGame(); - if (game.isonline) { - game.bragtrigger = trigger; - $.post("webrag.php", game, function (data, textStatus, request) { - if (data.alert) - alert(data.alert); - }, "json"); - } -} - +// Copyright (c)2002-2010 Eric Fredricksen all rights reserved + +var game = {}; +var lasttick, timerid; + +function timeGetTime() { + return new Date().getTime(); +} + +function StartTimer() { + if (!timerid) { + lasttick = timeGetTime(); + timerid = setTimeout(Timer1Timer, 100); + } +} + +function StopTimer() { + clearTimeout(timerid); + timerid = null; +} + +function Q(s) { + game.queue.push(s); + Dequeue(); +} + +function TaskDone() { + return TaskBar.done(); +} + +function Odds(chance, outof) { + return Random(outof) < chance; +} + +function RandSign() { + return Random(2) * 2 - 1; +} + +function RandomLow(below) { + return Min(Random(below), Random(below)); +} + +function PickLow(s) { + return s[RandomLow(s.length)]; +} + +function Copy(s, b, l) { + return s.substr(b-1, l); +} + +function Length(s) { + return s.length; +} + +function Starts(s, pre) { + return 0 === s.indexOf(pre); +} + +function Ends(s, e) { + return Copy(s, 1+Length(s)-Length(e), Length(e)) == e; +} + +function Plural(s) { + if (Ends(s,'y')) + return Copy(s,1,Length(s)-1) + 'ies'; + else if (Ends(s,'us')) + return Copy(s,1,Length(s)-2) + 'i'; + else if (Ends(s,'ch') || Ends(s,'x') || Ends(s,'s') || Ends(s, 'sh')) + return s + 'es'; + else if (Ends(s,'f')) + return Copy(s,1,Length(s)-1) + 'ves'; + else if (Ends(s,'man') || Ends(s,'Man')) + return Copy(s,1,Length(s)-2) + 'en'; + else return s + 's'; +} + +function Split(s, field, separator) { + return s.split(separator || "|")[field]; +} + +function Indefinite(s, qty) { + if (qty == 1) { + if (Pos(s.charAt(0), 'AEIOUÜaeiouü') > 0) + return 'an ' + s; + else + return 'a ' + s; + } else { + return IntToStr(qty) + ' ' + Plural(s); + } +} + +function Definite(s, qty) { + if (qty > 1) + s = Plural(s); + return 'the ' + s; +} + +function prefix(a, m, s, sep) { + if (sep == undefined) sep = ' '; + m = Abs(m); + if (m < 1 || m > a.length) return s; // In case of screwups + return a[m-1] + sep + s; +} + +function Sick(m, s) { + m = 6 - Abs(m); + return prefix(['dead','comatose','crippled','sick','undernourished'], m, s); +} + + +function Young(m, s) { + m = 6 - Abs(m); + return prefix(['foetal','baby','preadolescent','teenage','underage'], m, s); +} + + +function Big(m, s) { + return prefix(['greater','massive','enormous','giant','titanic'], m, s); +} + +function Special(m, s) { + if (Pos(' ', s) > 0) + return prefix(['veteran','cursed','warrior','undead','demon'], m, s); + else + return prefix(['Battle-','cursed ','Were-','undead ','demon '], m, s, ''); +} + +function InterplotCinematic() { + switch (Random(3)) { + case 0: + Q('task|1|Exhausted, you arrive at a friendly oasis in a hostile land'); + Q('task|2|You greet old friends and meet new allies'); + Q('task|2|You are privy to a council of powerful do-gooders'); + Q('task|1|There is much to be done. You are chosen!'); + break; + case 1: + Q('task|1|Your quarry is in sight, but a mighty enemy bars your path!'); + var nemesis = NamedMonster(GetI(Traits,'Level')+3); + Q('task|4|A desperate struggle commences with ' + nemesis); + var s = Random(3); + for (var i = 1; i <= Random(1 + game.act + 1); ++i) { + s += 1 + Random(2); + switch (s % 3) { + case 0: Q('task|2|Locked in grim combat with ' + nemesis); break; + case 1: Q('task|2|' + nemesis + ' seems to have the upper hand'); break; + case 2: Q('task|2|You seem to gain the advantage over ' + nemesis); break; + } + } + Q('task|3|Victory! ' + nemesis + ' is slain! Exhausted, you lose conciousness'); + Q('task|2|You awake in a friendly place, but the road awaits'); + break; + case 2: + var nemesis2 = ImpressiveGuy(); + Q("task|2|Oh sweet relief! You've reached the protection of the good " + nemesis2); + Q('task|3|There is rejoicing, and an unnerving encouter with ' + nemesis2 + ' in private'); + Q('task|2|You forget your ' + BoringItem() + ' and go back to get it'); + Q("task|2|What's this!? You overhear something shocking!"); + Q('task|2|Could ' + nemesis2 + ' be a dirty double-dealer?'); + Q('task|3|Who can possibly be trusted with this news!? ... Oh yes, of course'); + break; + } + Q('plot|1|Loading'); +} + + +function StrToInt(s) { + return parseInt(s, 10); +} + +function IntToStr(i) { + return i + ""; +} + +function NamedMonster(level) { + var lev = 0; + var result = ''; + for (var i = 0; i < 5; ++i) { + var m = Pick(K.Monsters); + if (!result || (Abs(level-StrToInt(Split(m,1))) < Abs(level-lev))) { + result = Split(m,0); + lev = StrToInt(Split(m,1)); + } + } + return GenerateName() + ' the ' + result; +} + +function ImpressiveGuy() { + return Pick(K.ImpressiveTitles) + + (Random(2) ? ' of the ' + Pick(K.Races) : ' of ' + GenerateName()); +} + +function MonsterTask(level) { + var definite = false; + for (var i = level; i >= 1; --i) { + if (Odds(2,5)) + level += RandSign(); + } + if (level < 1) level = 1; + // level = level of puissance of opponent(s) we'll return + + var monster, lev; + if (Odds(1,25)) { + // Use an NPC every once in a while + monster = ' ' + Split(Pick(K.Races), 0); + if (Odds(1,2)) { + monster = 'passing' + monster + ' ' + Split(Pick(K.Klasses), 0); + } else { + monster = PickLow(K.Titles) + ' ' + GenerateName() + ' the' + monster; + definite = true; + } + lev = level; + monster = monster + '|' + IntToStr(level) + '|*'; + } else if (game.questmonster && Odds(1,4)) { + // Use the quest monster + monster = K.Monsters[game.questmonsterindex]; + lev = StrToInt(Split(monster,1)); + } else { + // Pick the monster out of so many random ones closest to the level we want + monster = Pick(K.Monsters); + lev = StrToInt(Split(monster,1)); + for (var ii = 0; ii < 5; ++ii) { + var m1 = Pick(K.Monsters); + if (Abs(level-StrToInt(Split(m1,1))) < Abs(level-lev)) { + monster = m1; + lev = StrToInt(Split(monster,1)); + } + } + } + + var result = Split(monster,0); + game.task = 'kill|' + monster; + + var qty = 1; + if (level-lev > 10) { + // lev is too low. multiply... + qty = Math.floor((level + Random(Max(lev,1))) / Max(lev,1)); + if (qty < 1) qty = 1; + level = Math.floor(level / qty); + } + + if ((level - lev) <= -10) { + result = 'imaginary ' + result; + } else if ((level-lev) < -5) { + i = 10+(level-lev); + i = 5-Random(i+1); + result = Sick(i,Young((lev-level)-i,result)); + } else if (((level-lev) < 0) && (Random(2) == 1)) { + result = Sick(level-lev,result); + } else if (((level-lev) < 0)) { + result = Young(level-lev,result); + } else if ((level-lev) >= 10) { + result = 'messianic ' + result; + } else if ((level-lev) > 5) { + i = 10-(level-lev); + i = 5-Random(i+1); + result = Big(i,Special((level-lev)-i,result)); + } else if (((level-lev) > 0) && (Random(2) == 1)) { + result = Big(level-lev,result); + } else if (((level-lev) > 0)) { + result = Special(level-lev,result); + } + + lev = level; + level = lev * qty; + + if (!definite) result = Indefinite(result, qty); + return { 'description': result, 'level': level }; +} + +function LowerCase(s) { + return s.toLowerCase(); +} + +function ProperCase(s) { + return Copy(s,1,1).toUpperCase() + Copy(s,2,10000); +} + +function EquipPrice() { + return 5 * GetI(Traits,'Level') * GetI(Traits,'Level') + + 10 * GetI(Traits,'Level') + + 20; +} + +function Dequeue() { + while (TaskDone()) { + if (Split(game.task,0) == 'kill') { + if (Split(game.task,3) == '*') { + WinItem(); + } else if (Split(game.task,3)) { + Add(Inventory,LowerCase(Split(game.task,1) + ' ' + + ProperCase(Split(game.task,3))),1); + } + } else if (game.task == 'buying') { + // buy some equipment + Add(Inventory,'Gold',-EquipPrice()); + WinEquip(); + } else if ((game.task == 'market') || (game.task == 'sell')) { + if (game.task == 'sell') { + var amt = GetI(Inventory, 1) * GetI(Traits,'Level'); + if (Pos(' of ', Inventory.label(1)) > 0) + amt *= (1+RandomLow(10)) * (1+RandomLow(GetI(Traits,'Level'))); + Inventory.remove1(); + Add(Inventory, 'Gold', amt); + } + if (Inventory.length() > 1) { + Inventory.scrollToTop(); + Task('Selling ' + Indefinite(Inventory.label(1), GetI(Inventory,1)), + 1 * 1000); + game.task = 'sell'; + break; + } + } + + var old = game.task; + game.task = ''; + if (game.queue.length > 0) { + var a = Split(game.queue[0],0); + var n = StrToInt(Split(game.queue[0],1)); + var s = Split(game.queue[0],2); + if (a == 'task' || a == 'plot') { + game.queue.shift(); + if (a == 'plot') { + CompleteAct(); + s = 'Loading ' + game.bestplot; + } + Task(s, n * 1000); + } else { + throw 'bah!' + a; + } + } else if (EncumBar.done()) { + Task('Heading to market to sell loot',4 * 1000); + game.task = 'market'; + } else if ((Pos('kill|',old) <= 0) && (old != 'heading')) { + if (GetI(Inventory, 'Gold') > EquipPrice()) { + Task('Negotiating purchase of better equipment', 5 * 1000); + game.task = 'buying'; + } else { + Task('Heading to the killing fields', 4 * 1000); + game.task = 'heading'; + } + } else { + var nn = GetI(Traits, 'Level'); + var t = MonsterTask(nn); + var InventoryLabelAlsoGameStyleTag = 3; + nn = Math.floor((2 * InventoryLabelAlsoGameStyleTag * t.level * 1000) / nn); + Task('Executing ' + t.description, nn); + } + } +} + + +function Put(list, key, value) { + if (typeof key === typeof 1) + key = list.label(key); + + if (list.fixedkeys) { + game[list.id][key] = value; + } else { + var i = 0; + for (; i < game[list.id].length; ++i) { + if (game[list.id][i][0] === key) { + game[list.id][i][1] = value; + break; + } + } + if (i == game[list.id].length) + game[list.id].push([key,value]); + } + + list.PutUI(key, value); + + if (key === 'STR') + EncumBar.reset(10 + value, EncumBar.Position()); + + if (list === Inventory) { + var cubits = 0; + $.each(game.Inventory.slice(1), function (index, item) { + cubits += StrToInt(item[1]); + }); + EncumBar.reposition(cubits); + } +} + + +function ProgressBar(id, tmpl) { + this.id = id; + this.bar = $("#"+ id + " > .bar"); + this.tmpl = tmpl; + + this.Max = function () { return game[this.id].max; }; + this.Position = function () { return game[this.id].position; }; + + this.reset = function (newmax, newposition) { + game[this.id].max = newmax; + this.reposition(newposition || 0); + }; + + this.reposition = function (newpos) { + game[this.id].position = Min(newpos, this.Max()); + + // Recompute hint + game[this.id].percent = (100 * this.Position()).div(this.Max()); + game[this.id].remaining = Math.floor(this.Max() - this.Position()); + game[this.id].time = RoughTime(this.Max() - this.Position()); + game[this.id].hint = template(this.tmpl, game[this.id]); + + // Update UI + if (this.bar) { + var p = this.Max() ? 100 * this.Position() / this.Max() : 0; + this.bar.css("width", p + "%"); + this.bar.parent().find(".hint").text(game[this.id].hint); + } + }; + + this.increment = function (inc) { + this.reposition(this.Position() + inc); + }; + + this.done = function () { + return this.Position() >= this.Max(); + }; + + this.load = function (game) { + this.reposition(this.Position()); + }; +} + + + +function Key(tr) { + return $(tr).children().first().text(); +} + +function Value(tr) { + return $(tr).children().last().text(); +} + + + +function ListBox(id, columns, fixedkeys) { + this.id = id; + this.box = $("tbody#_, #_ tbody".replace(/_/g, id)); + this.columns = columns; + this.fixedkeys = fixedkeys; + + this.AddUI = function (caption) { + if (!this.box) return; + var tr = $(" " + + caption + ""); + tr.appendTo(this.box); + tr.each(function () {this.scrollIntoView();}); + return tr; + }; + + this.ClearSelection = function () { + if (this.box) + this.box.find("tr").removeClass("selected"); + }; + + this.PutUI = function (key, value) { + if (!this.box) return; + var item = this.rows().filter(function (index) { + return Key(this) === key; + }); + if (!item.length) { + item = $("" + key + ""); + this.box.append(item); + } + + item.children().last().text(value); + item.addClass("selected"); + item.each(function () {this.scrollIntoView();}); + }; + + this.scrollToTop = function () { + if (this.box) + this.box.parents(".scroll").scrollTop(0); + }; + + this.rows = function () { + return this.box.find("tr").has("td"); + }; + + this.CheckAll = function (butlast) { + if (this.box) { + if (butlast) + this.rows().find("input:checkbox").not(':last').attr("checked","true"); + else + this.rows().find("input:checkbox").attr("checked","true"); + } + }; + + this.length = function () { + return (this.fixedkeys || game[this.id]).length; + }; + + this.remove0 = function (n) { + if (game[this.id]) + game[this.id].shift(); + if (this.box) + this.box.find("tr").first().remove(); + }; + + this.remove1 = function (n) { + var t = game[this.id].shift(); + game[this.id].shift(); + game[this.id].unshift(t); + if (this.box) + this.box.find("tr").eq(1).remove(); + }; + + + this.load = function (game) { + var that = this; + var dict = game[this.id]; + if (this.fixedkeys) { + $.each(this.fixedkeys, function (index, key) { + that.PutUI(key, dict[key]); + }); + } else { + $.each(dict, function (index, row) { + if (that.columns == 2) + that.PutUI(row[0], row[1]); + else + that.AddUI(row); + }); + } + }; + + + this.label = function (n) { + return this.fixedkeys ? this.fixedkeys[n] : game[this.id][n][0]; + }; +} + + +var ExpBar, PlotBar, TaskBar, QuestBar, EncumBar; +var Traits,Stats,Spells,Equips,Inventory,Plots,Quests; +var Kill; +var AllBars, AllLists; + + +function StrToIntDef(s, def) { + var result = parseInt(s, 10); + return isNaN(result) ? def : result; +} + + +if (document) + $(document).ready(FormCreate); + + +function WinSpell() { + AddR(Spells, K.Spells[RandomLow(Min(GetI(Stats,'WIS')+GetI(Traits,'Level'), + K.Spells.length))], 1); +} + +function LPick(list, goal) { + var result = Pick(list); + for (var i = 1; i <= 5; ++i) { + var best = StrToInt(Split(result, 1)); + var s = Pick(list); + var b1 = StrToInt(Split(s,1)); + if (Abs(goal-best) > Abs(goal-b1)) + result = s; + } + return result; +} + +function Abs(x) { + if (x < 0) return -x; else return x; +} + +function WinEquip() { + var posn = Random(Equips.length()); + + if (!posn) { + stuff = K.Weapons; + better = K.OffenseAttrib; + worse = K.OffenseBad; + } else { + better = K.DefenseAttrib; + worse = K.DefenseBad; + stuff = (posn == 1) ? K.Shields: K.Armors; + } + var name = LPick(stuff, GetI(Traits,'Level')); + var qual = StrToInt(Split(name,1)); + name = Split(name,0); + var plus = GetI(Traits,'Level') - qual; + if (plus < 0) better = worse; + var count = 0; + while (count < 2 && plus) { + var modifier = Pick(better); + qual = StrToInt(Split(modifier, 1)); + modifier = Split(modifier, 0); + if (Pos(modifier, name) > 0) break; // no repeats + if (Abs(plus) < Abs(qual)) break; // too much + name = modifier + ' ' + name; + plus -= qual; + ++count; + } + if (plus) name = plus + ' ' + name; + if (plus > 0) name = '+' + name; + + Put(Equips, posn, name); + game.bestequip = name; + if (posn > 1) game.bestequip += ' ' + Equips.label(posn); +} + + +function Square(x) { return x * x; } + +function WinStat() { + var i; + if (Odds(1,2)) { + i = Pick(K.Stats); + } else { + // Favor the best stat so it will tend to clump + var t = 0; + $.each(K.Stats, function (index, key) { + t += Square(GetI(Stats, key)); + }); + t = Random(t); + $.each(K.Stats, function (index, key) { + i = key; + t -= Square(GetI(Stats, key)); + if (t < 0) return false; + }); + } + Add(Stats, i, 1); +} + +function SpecialItem() { + return InterestingItem() + ' of ' + Pick(K.ItemOfs); +} + +function InterestingItem() { + return Pick(K.ItemAttrib) + ' ' + Pick(K.Specials); +} + +function BoringItem() { + return Pick(K.BoringItems); +} + +function WinItem() { + Add(Inventory, SpecialItem(), 1); +} + +function CompleteQuest() { + QuestBar.reset(50 + RandomLow(1000)); + if (Quests.length()) { + Log('Quest completed: ' + game.bestquest); + Quests.CheckAll(); + [WinSpell,WinEquip,WinStat,WinItem][Random(4)](); + } + while (Quests.length() > 99) + Quests.remove0(); + + game.questmonster = ''; + var caption; + switch (Random(5)) { + case 0: + var level = GetI(Traits,'Level'); + var lev = 0; + for (var i = 1; i <= 4; ++i) { + var montag = Random(K.Monsters.length); + var m = K.Monsters[montag]; + var l = StrToInt(Split(m,1)); + if (i == 1 || Abs(l - level) < Abs(lev - level)) { + lev = l; + game.questmonster = m; + game.questmonsterindex = montag; + } + } + caption = 'Exterminate ' + Definite(Split(game.questmonster,0),2); + break; + case 1: + caption = 'Seek ' + Definite(InterestingItem(), 1); + break; + case 2: + caption = 'Deliver this ' + BoringItem(); + break; + case 3: + caption = 'Fetch me ' + Indefinite(BoringItem(), 1); + break; + case 4: + var mlev = 0; + level = GetI(Traits,'Level'); + for (var ii = 1; ii <= 2; ++ii) { + montag = Random(K.Monsters.length); + m = K.Monsters[montag]; + l = StrToInt(Split(m,1)); + if ((ii == 1) || (Abs(l - level) < Abs(mlev - level))) { + mlev = l; + game.questmonster = m; + } + } + caption = 'Placate ' + Definite(Split(game.questmonster,0),2); + game.questmonster = ''; // We're trying to placate them, after all + break; + } + if (!game.Quests) game.Quests = []; + while (game.Quests.length > 99) game.Quests.shift(); + game.Quests.push(caption); + game.bestquest = caption; + Quests.AddUI(caption); + + + Log('Commencing quest: ' + caption); + + SaveGame(); +} + +function toRoman(n) { + if (!n) return "N"; + var s = ""; + function _rome(dn,ds) { + if (n >= dn) { + n -= dn; + s += ds; + return true; + } else return false; + } + if (n < 0) { + s = "-"; + n = -n; + } + while (_rome(1000,"M")) {0;} + _rome(900,"CM"); + _rome(500,"D"); + _rome(400,"CD"); + while (_rome(100,"C")) {0;} + _rome(90,"XC"); + _rome(50,"L"); + _rome(40,"XL"); + while (_rome(10,"X")) {0;} + _rome(9,"IX"); + _rome(5,"V"); + _rome(4,"IV"); + while (_rome(1,"I")) {0;} + return s; +} + +function toArabic(s) { + n = 0; + s = s.toUpperCase(); + function _arab(ds,dn) { + if (!Starts(s, ds)) return false; + s = s.substr(ds.length); + n += dn; + return true; + } + while (_arab("M",1000)) {0;} + _arab("CM",900); + _arab("D",500); + _arab("CD",400); + while (_arab("C",100)) {0;} + _arab("XC",90); + _arab("L",50); + _arab("XL",40); + while (_arab("X",10)) {0;} + _arab("IX",9); + _arab("V",5); + _arab("IV",4); + while (_arab("I",1)) {0;} + return n; +} + +function CompleteAct() { + Plots.CheckAll(); + game.act += 1; + PlotBar.reset(60 * 60 * (1 + 5 * game.act)); // 1 hr + 5/act + Plots.AddUI((game.bestplot = 'Act ' + toRoman(game.act))); + + if (game.act > 1) { + WinItem(); + WinEquip(); + } + + Brag('act'); +} + + +function Log(line) { + if (game.log) + game.log[+new Date()] = line; + // TODO: and now what? +} + +function Task(caption, msec) { + game.kill = caption + "..."; + if (Kill) + Kill.text(game.kill); + Log(game.kill); + TaskBar.reset(msec); +} + +function Add(list, key, value) { + Put(list, key, value + GetI(list,key)); + + /*$IFDEF LOGGING*/ + if (!value) return; + var line = (value > 0) ? "Gained" : "Lost"; + if (key == 'Gold') { + key = "gold piece"; + line = (value > 0) ? "Got paid" : "Spent"; + } + if (value < 0) value = -value; + line = line + ' ' + Indefinite(key, value); + Log(line); + /*$ENDIF*/ +} + +function AddR(list, key, value) { + Put(list, key, toRoman(value + toArabic(Get(list,key)))); +} + +function Get(list, key) { + if (list.fixedkeys) { + if (typeof key === typeof 1) + key = list.fixedkeys[key]; + return game[list.id][key]; + } else if (typeof key === typeof 1) { + if (key < game[list.id].length) + return game[list.id][key][1]; + else + return ""; + } else { + for (var i = 0; i < game[list.id].length; ++i) { + if (game[list.id][i][0] === key) + return game[list.id][i][1]; + } + return ""; + } +} + +function GetI(list, key) { + return StrToIntDef(Get(list,key), 0); +} + +function Min(a,b) { + return a < b ? a : b; +} + +function Max(a,b) { + return a > b ? a : b; +} + +function LevelUp() { + Add(Traits,'Level',1); + Add(Stats,'HP Max', GetI(Stats,'CON').div(3) + 1 + Random(4)); + Add(Stats,'MP Max', GetI(Stats,'INT').div(3) + 1 + Random(4)); + WinStat(); + WinStat(); + WinSpell(); + ExpBar.reset(LevelUpTime(GetI(Traits,'Level'))); + Brag('level'); +} + +function ClearAllSelections() { + $.each(AllLists, function () {this.ClearSelection();}); +} + +function RoughTime(s) { + if (s < 120) return s + ' seconds'; + else if (s < 60 * 120) return s.div(60) + ' minutes'; + else if (s < 60 * 60 * 48) return s.div(3600) + ' hours'; + else if (s < 60 * 60 * 24 * 60) return s.div(3600 * 24) + ' days'; + else if (s < 60 * 60 * 24 * 30 * 24) return s.div(3600 * 24 * 30) +" months"; + else return s.div(3600 * 24 * 30 * 12) + " years"; + +} + +function Pos(needle, haystack) { + return haystack.indexOf(needle) + 1; +} + +var dealing = false; + +function Timer1Timer() { + timerid = null; // Event has fired + if (TaskBar.done()) { + game.tasks += 1; + game.elapsed += TaskBar.Max().div(1000); + + ClearAllSelections(); + + if (game.kill == 'Loading....') + TaskBar.reset(0); // Not sure if this is still the ticket + + // gain XP / level up + var gain = Pos('kill|', game.task) == 1; + if (gain) { + if (ExpBar.done()) + LevelUp(); + else + ExpBar.increment(TaskBar.Max() / 1000); + } + + // advance quest + if (gain && game.act >= 1) { + if (QuestBar.done() || !Quests.length()) { + CompleteQuest(); + } else { + QuestBar.increment(TaskBar.Max() / 1000); + } + } + + // advance plot + if (gain || !game.act) { + if (PlotBar.done()) + InterplotCinematic(); + else + PlotBar.increment(TaskBar.Max() / 1000); + } + + Dequeue(); + } else { + var elapsed = timeGetTime() - lasttick; + if (elapsed > 100) elapsed = 100; + if (elapsed < 0) elapsed = 0; + TaskBar.increment(elapsed); + } + + StartTimer(); +} + +function FormCreate() { + ExpBar = new ProgressBar("ExpBar", "$remaining XP needed for next level"); + EncumBar = new ProgressBar("EncumBar", "$position/$max cubits"); + PlotBar = new ProgressBar("PlotBar", "$time remaining"); + QuestBar = new ProgressBar("QuestBar", "$percent% complete"); + TaskBar = new ProgressBar("TaskBar", "$percent%"); + + AllBars = [ExpBar,PlotBar,TaskBar,QuestBar,EncumBar]; + + Traits = new ListBox("Traits", 2, K.Traits); + Stats = new ListBox("Stats", 2, K.Stats); + Spells = new ListBox("Spells", 2); + Equips = new ListBox("Equips", 2, K.Equips); + Inventory = new ListBox("Inventory", 2); + Plots = new ListBox("Plots", 1); + Quests = new ListBox("Quests", 1); + + Plots.load = function (sheet) { + for (var i = Max(0, game.act-99); i <= game.act; ++i) + this.AddUI(i ? 'Act ' + toRoman(i) : "Prologue"); + + }; + + AllLists = [Traits,Stats,Spells,Equips,Inventory,Plots,Quests]; + + if (document) { + Kill = $("#Kill"); + + $("#quit").click(quit); + + $(document).keypress(FormKeyDown); + + $(document).bind('beforeunload', function () { + if (!storage) + return "Are you sure you want to quit? All your progress will be lost!"; + }); + + $(window).unload(function (event) { + StopTimer(); + SaveGame(); + if (storage.async) { + // Have to give SQL transaction a chance to complete + if (window.showModalDialog) + pause(100); + + // Just accept some data loss - alert is too ugly. Maybe increase save + // frequency. + // else alert("Game saved"); + } + }); + + if (iOS) $("body").addClass("iOS"); + } + + var name = unescape(window.location.href.split('#')[1]); + storage.loadSheet(name, LoadGame); +} + + + +function pause(msec) { + window.showModalDialog("javascript:document.writeln ('')", + null, + "dialogWidth:0;dialogHeight:0;dialogHide:yes;unadorned:yes;"+ + "status:no;scroll:no;center:no;dialogTop:-10000;dialogLeft:-10000"); +} + +function quit() { + $(window).unbind('unload'); + SaveGame(function () { window.location.href = "roster.html"; }); +} + + +function HotOrNot() { + // Figure out which spell is best + if (Spells.length()) { + var flat = 1; // Flattening constant + var best = 0, i; + for (i = 1; i < Spells.length(); ++i) { + if ((i+flat) * toArabic(Get(Spells,i)) > + (best+flat) * toArabic(Get(Spells,best))) + best = i; + } + game.bestspell = Spells.label(best) + ' ' + Get(Spells, best); + } else { + game.bestspell = ''; + } + + /// And which stat is best? + best = 0; + for (i = 1; i <= 5; ++i) { + if (GetI(Stats,i) > GetI(Stats,best)) + best = i; + } + game.beststat = Stats.label(best) + ' ' + GetI(Stats, best); +} + + +function SaveGame(callback) { + Log('Saving game: ' + GameSaveName()); + HotOrNot(); + game.date = ''+new Date(); + game.stamp = +new Date(); + game.seed = randseed(); + storage.addToRoster(game, callback); +} + +function LoadGame(sheet) { + if (!sheet) { + alert("Error loading game"); + window.location.href = "roster.html"; + return; + } + + game = sheet; + + /* + if (!window.localStorage) { + // Cookies can't hold a whole game save + storage.removeItem("roster"); + storage = null; + } +*/ + + if (document) { + var title = "Progress Quest - " + GameSaveName(); + $("#title").text(title); + if (iOS) title = GameSaveName(); + document.title = title; + } + + + randseed(game.seed); + $.each(AllBars.concat(AllLists), function (i, e) { e.load(game); }); + if (Kill) + Kill.text(game.kill); + ClearAllSelections(); + $.each([Plots,Quests], function () { + this.CheckAll(true); + }); + Log('Loaded game: ' + game.Traits.Name); + if (!game.elapsed) + Brag('start'); + StartTimer(); +} + +function GameSaveName() { + if (!game.saveName) { + game.saveName = Get(Traits, 'Name'); + if (game.realm) + game.saveName += ' [' + game.realm + ']'; + } + return game.saveName; +} + + +function InputBox(message, def) { + var i = prompt(message, def || ''); + return (i !== null) ? i : def; +} + +function ToDna(s) { + s = s + ""; + var code = { + '0': "AT", + '1': "AG", + '2': "AC", + '3': "TA", + '4': "TG", + '5': "TC", + '6': "GA", + '7': "GT", + '8': "GC", + '9': "CA", + ',': "CT", + '.': "CG" + }; + var r = ""; + for (var i = 0; i < s.length; ++i) { + r += code[s[i]]; + if (i && (i % 4) == 0) r += " "; + } + return r; +} + +function FormKeyDown(e) { + var key = String.fromCharCode(e.which); + + if (key === 'd') { + alert("Your character's genome is " + ToDna(game.dna + "")); + } + + if (game.isonline) { + if (key === 'b') { + Brag('brag'); + //Navigate(GetHostAddr() + 'name=' + UrlEncode(Get(Traits,'Name'))); + } + + if (key === 'g') { + game.guild = InputBox('Choose a guild.\r\rMake sure you undestand the guild rules before you join one. To learn more about guilds, visit http://progressquest.com/guilds.php', game.guild); + Brag("guild"); + } + + if (key === 'm') { + game.motto = InputBox('Declare your motto!', game.motto); + Brag('motto'); + } + } + + if (key === 'p') { + if (timerid) { + $('#paused').css('display', 'block'); + StopTimer(); + } else { + $('#paused').css('display', ''); + StartTimer(); + } + } + + if (key === 'q') { + quit(); + } + + if (key === 's') { + SaveGame(); + alert('Saved (' + JSON.stringify(game).length + ' bytes).'); + } + + /* + if (key === 't') { + TaskBar.reposition(TaskBar.Max()); + } + */ +} + +function Navigate(url) { + window.open(url); +} + +function LFSR(pt, salt) { + var result = salt; + for (var k = 1; k <= Length(pt); ++k) + result = Ord(pt[k]) ^ (result << 1) ^ (1 && ((result >> 31) ^ (result >> 5))); + for (var kk = 1; kk <= 10; ++kk) + result = (result << 1) ^ (1 && ((result >> 31) ^ (result >> 5))); +} + + +function Brag(trigger) { + SaveGame(); + if (game.isonline) { + game.bragtrigger = trigger; + $.post("webrag.php", game, function (data, textStatus, request) { + if (data.alert) + alert(data.alert); + }, "json"); + } +} +