Files
ProgressQuest/Main.pas
T
Eric Fredricksen 51792e632d [contents of README]
ProgressQuest v6.2(probably pre-release)
-----------------------------------------
This directory represents an ex post facto rendering of pq6 in its
various versions in svn, so that changes will be trackable thataway.

The version as of this moment in svn is 6.2, but this is probably not
the final 6.2. That will come in the next checkin.

(I didn't add this file in PQ 6.0, but after that you can use this README
to trace the checkin history using svn.)
2006-12-24 20:08:53 +00:00

1548 lines
44 KiB
ObjectPascal

unit Main;
{ copyright (c)2002 Eric Fredricksen all rights reserved }
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls, ExtCtrls, Buttons, ImgList, Menus, ShellAPI;
const
RevString = '&rev=4';
wmIconTray = WM_USER + Ord('t');
kFileExt = '.pq';
type
TMainForm = class(TForm)
Panel1: TPanel;
Label1: TLabel;
Traits: TListView;
Equips: TListView;
Panel3: TPanel;
Label3: TLabel;
QuestBar: TProgressBar;
Stats: TListView;
Label2: TLabel;
PlotBar: TProgressBar;
Plots: TListView;
Quests: TListView;
Panel2: TPanel;
Label4: TLabel;
Spells: TListView;
InventoryLabelAlsoGameStyle: TLabel;
Inventory: TListView;
Panel4: TPanel;
Kill: TStatusBar;
Label6: TLabel;
ExpBar: TProgressBar;
TaskBar: TProgressBar;
Timer1: TTimer;
EncumBar: TProgressBar;
Label7: TLabel;
ImageList1: TImageList;
Label8: TLabel;
Cheats: TPanel;
CashIn: TButton;
Button1: TButton;
FinishQuest: TButton;
Button3: TButton;
CheatPlot: TButton;
vars: TPanel;
fTask: TLabel;
fQuest: TLabel;
fQueue: TListBox;
procedure GoButtonClick(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure CashInClick(Sender: TObject);
procedure FinishQuestClick(Sender: TObject);
procedure CheatPlotClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
private
procedure Task(caption: String; msec: Integer);
procedure Dequeue;
procedure Q(s: string);
function TaskDone: Boolean;
procedure CompleteQuest;
procedure CompleteAct;
procedure WinEquip;
procedure WinSpell;
procedure WinStat;
procedure WinItem;
function SpecialItem: String;
procedure LevelUp;
function BoringItem: String;
function InterestingItem: String;
function MonsterTask(var level: Integer): String;
function EquipPrice: Integer;
procedure Brag(trigger: String);
procedure TriggerAutosizes;
function GameSaveName: String;
procedure OnTrayMessage(var Msg: TMessage); message wmIconTray;
procedure OnSysCommand(var Msg : TWMSysCommand); message WM_SYSCOMMAND;
procedure Guildify;
procedure ClearAllSelections;
procedure OnQueryEndSession(var Msg : TMessage); message WM_QUERYENDSESSION;
procedure OnEndSession(var Msg : TMessage); message WM_ENDSESSION;
procedure RestoreIt;
function AuthenticateUrl(url: String): String;
procedure Log(line: String);
procedure ExportCharSheet;
public
FTrayIcon: TNotifyIconData;
FReportSave: Boolean;
FLogEvents: Boolean;
FMakeBackups: Boolean;
FMinToTray: Boolean;
FExportSheets: Boolean;
FSaveFileName: String;
procedure MinimizeIt;
procedure LoadGame(name: String);
function SaveGame: Boolean;
procedure Put(list: TListView; key: String; value: String); overload;
procedure Put(list: TListView; pos: Integer; value: String); overload;
procedure Put(list: TListView; key: String; value: Integer); overload;
procedure Add(list: TListView; key: String; value: Integer); overload;
procedure AddR(list: TListView; key: String; value: Integer); overload;
function Get(list: TListView; key: String): String; overload;
function Get(list: TListView; index: Integer): String; overload;
function GetI(list: TListView; key: String): Integer; overload;
function GetI(list: TListView; index: Integer): Integer; overload;
function Sum(list: TListView): Integer;
function RollCharacter: Boolean;
function GetMotto: String;
function GetPasskey: Integer;
procedure SetMotto(v: String);
procedure SetPasskey(v: String);
function GetHostAddr: String;
function GetHostName: String;
procedure SetHostAddr(v: String);
procedure SetHostName(v: String);
function GetLogin: String;
function GetPassword: String;
procedure SetLogin(v: String);
procedure SetPassword(v: String);
function GetGuild: String;
procedure SetGuild(v: String);
end;
var
MainForm: TMainForm;
function Split(s: String; field: Integer): String;
procedure Navigate(url: String);
implementation
uses Web, StrUtils, NewGuy, Math, Config, Front, zlibex, SelServ, Login,
mmsystem, Registry, ShlObj;
{$R *.dfm}
{$DEFINE CHEATS}
// Returns '' if not there, which is lame, but okay for my purposes
function RegRead(root: HKEY; path, name: String): String;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := root;
if Reg.OpenKey(path, false) then
Result := Reg.ReadString(name);
Reg.CloseKey;
finally
Reg.Free;
end;
end;
procedure RegWrite(root: HKEY; path, name, value: String);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := root;
Reg.OpenKey(path, true);
Reg.WriteString(name, value);
Reg.CloseKey;
finally
Reg.Free;
end;
end;
procedure MakeFileAssociations;
const
kPQFileType = 'ProgressQuest.GameSave';
var
kOpenCommand: String;
begin
kOpenCommand := '"' + Application.ExeName + '" "%1"';
RegWrite(HKEY_CLASSES_ROOT, kFileExt,'', kPQFileType);
RegWrite(HKEY_CLASSES_ROOT, kPQFileType, '', 'Progresss Quest saved game');
RegWrite(HKEY_CLASSES_ROOT, kPQFileType + '\DefaultIcon', '', Application.ExeName + ',0');
RegWrite(HKEY_CLASSES_ROOT, kPQFileType + '\Shell\Open', '', '&Open');
if RegRead(HKEY_CLASSES_ROOT, kPQFileType + '\Shell\Open\Command', '') <> kOpenCommand then begin
RegWrite(HKEY_CLASSES_ROOT, kPQFileType + '\Shell\Open\Command', '', kOpenCommand);
// Notify Windows Explorer to realize we added this. In case this is slow
// I don't do this unless I'm sure this one has changed.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
end;
end;
procedure TMainForm.MinimizeIt;
begin
if not FMinToTray then Exit;
with FTrayIcon do
begin
cbSize := SizeOf(FTrayIcon);
Wnd := Handle;
uID := 0;
uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP;
uCallbackMessage := wmIconTray;
hIcon := Application.Icon.Handle;
StrPCopy(szTip, Caption);
//a.szTip := 'Progress Quest';
end;
Application.Minimize;
ShowWindow(Application.Handle, SW_HIDE);
Shell_NotifyIcon(NIM_ADD,@FTrayIcon);
end;
procedure TMainForm.OnSysCommand(var Msg: TWMSysCommand);
begin
if (Msg.CmdType = SC_MINIMIZE) and FMinToTray then
MinimizeIt();
inherited;
end;
procedure TMainForm.RestoreIt;
begin
ShowWindow(Application.Handle, SW_SHOW);
Application.Restore;
Shell_NotifyIcon(NIM_DELETE, @MainForm.FTrayIcon);
end;
procedure TMainForm.OnTrayMessage(var Msg: TMessage);
//var p : TPoint;
begin
case Msg.lParam of
WM_LBUTTONDOWN, WM_RBUTTONDOWN:
RestoreIt;
end;
end;
procedure StartTimer;
begin
if not MainForm.Timer1.Enabled then begin
MainForm.Timer1.Tag := timeGetTime;
//Shell_NotifyIcon(NIM_ADD, @MainForm.FTrayIcon);
end;
MainForm.Timer1.Enabled := True;
// BS location for this but
MainForm.Caption := 'ProgressQuest - ' + ChangeFileExt(MainForm.GameSaveName, '');
end;
function TMainForm.GetPasskey: Integer; begin Result := Traits.Tag; end;
procedure TMainForm.SetPasskey(v: String);
begin
Traits.Hint := v;
Traits.Tag := StrToIntDef(Traits.Hint,0);
end;
function TMainForm.GetMotto: String; begin Result := Stats.Hint; end;
procedure TMainForm.SetMotto(v: String); begin Stats.Hint := v; end;
function TMainForm.GetHostName: String; begin Result := Spells.Hint; end;
procedure TMainForm.SetHostName(v: String); begin Spells.Hint := v; end;
function TMainForm.GetHostAddr: String;
begin
Result := Equips.Hint;
if (Result = '') and (GetPasskey <> 0) then
Result := 'http://www.progressquest.com/knoram.php?';
end;
procedure TMainForm.SetHostAddr(v: String); begin Equips.Hint := v; end;
function TMainForm.GetLogin: String; begin Result := Inventory.Hint; end;
procedure TMainForm.SetLogin(v: String); begin Inventory.Hint := v; end;
function TMainForm.GetPassword: String; begin Result := Plots.Hint; end;
procedure TMainForm.SetPassword(v: String); begin Plots.Hint := v; end;
function TMainForm.GetGuild: String; begin Result := Label1.Hint; end;
procedure TMainForm.SetGuild(v: String); begin Label1.Hint := v; end;
procedure TMainForm.Q(s: string);
begin
fQueue.Items.Add(s);
Dequeue;
end;
function TMainForm.TaskDone: Boolean;
begin
with TaskBar do
Result := Position >= Max;
end;
function Odds(chance, outof: Integer): Boolean;
begin
Result := Random(outof) < chance;
end;
function RandSign(): Integer;
begin
Result := Random(2) * 2 - 1;
end;
function Pick(s: TStrings): String;
begin
Result := s[Random(s.Count)];
end;
function RandomLow(below: Integer): Integer;
begin
Result := Min(Random(below),Random(below));
end;
function Ends(s,e: String): Boolean;
begin
Result := Copy(s,1+Length(s)-Length(e),Length(e)) = e;
end;
function Plural(s: String): String;
begin
if Ends(s,'y')
then Result := Copy(s,1,Length(s)-1) + 'ies'
else if Ends(s,'us')
then Result := Copy(s,1,Length(s)-2) + 'i'
else if Ends(s,'ch') or Ends(s,'x') or Ends(s,'s')
then Result := s + 'es'
else if Ends(s,'f')
then Result := Copy(s,1,Length(s)-1) + 'ves'
else if Ends(s,'man') or Ends(s,'Man')
then Result := Copy(s,1,Length(s)-2) + 'en'
else Result := s + 's';
end;
function Split(s: String; field: Integer): String;
var
p: Integer;
begin
while field > 0 do begin
p := Pos('|',s);
s := Copy(s,p+1,10000);
Dec(field);
end;
if Pos('|',s) > 0
then Result := Copy(s,1,Pos('|',s)-1)
else Result := s;
end;
function Indefinite(s: String; qty: Integer): String;
begin
if qty = 1 then begin
if Pos(s[1], 'AEIOUÜaeiouü') > 0
then Result := 'an ' + s
else Result := 'a ' + s;
end else begin
Result := IntToStr(qty) + ' ' + Plural(s);
end;
end;
function Definite(s: String; qty: Integer): String;
begin
if qty > 1 then
s := {IntToStr(qty) + ' ' +} Plural(s);
Result := 'the ' + s;
end;
function Sick(m: Integer; s: String): String;
begin
Result := IntToStr(m) + s; // in case I screw up
case m of
-5,5: Result := 'dead ' + s;
-4,4: Result := 'comatose ' + s;
-3,3: Result := 'crippled ' + s;
-2,2: Result := 'sick ' + s;
-1,1: Result := 'undernourished ' + s;
end;
end;
function Young(m: Integer; s: String): String;
begin
Result := IntToStr(m) + s; // in case I screw up
case -m of
-5,5: Result := 'fetal ' + s;
-4,4: Result := 'baby ' + s;
-3,3: Result := 'preadolescent ' + s;
-2,2: Result := 'teenage ' + s;
-1,1: Result := 'underage ' + s;
end;
end;
function Big(m: Integer; s: String): String;
begin
Result := s; // in case I screw up
case m of
1,-1: Result := 'greater ' + s;
2,-2: Result := 'massive ' + s;
3,-3: Result := 'enormous ' + s;
4,-4: Result := 'giant ' + s;
5,-5: Result := 'titanic ' + s;
end;
end;
function Special(m: Integer; s: String): String;
begin
Result := s; // in case I screw up
case -m of
1,-1:
if Pos(' ',Result) > 0
then Result := 'veteran ' + s
else Result := 'Battle-' + s;
2,-2: Result := 'cursed ' + s;
3,-3:
if Pos(' ',Result) > 0
then Result := 'warrior ' + s
else Result := 'Were-' + s;
4,-4: Result := 'undead ' + s;
5,-5: Result := 'demon ' + s;
end;
end;
function TMainForm.MonsterTask(var level: Integer): String;
var
qty, lev, i: Integer;
monster, m1: string;
begin
for i := level downto 1 do begin
if Odds(2,5) then
Inc(level, RandSign());
end;
if level < 1 then level := 1;
// level = level of puissance of opponent(s) we'll return
if Odds(1,25) then begin
// use an NPC every once in a while
monster := 'passing ' + Pick(NewGuyForm.Race.Items) + ' ' + Pick(NewGuyForm.Klass.Items);
lev := level;
monster := monster + '|' + IntToStr(level) + '|*';
end else if (fQuest.Caption <> '') and Odds(1,4) then begin
// use the quest monster
monster := k.Monsters.Lines[fQuest.Tag];
lev := StrToInt(Split(monster,1));
end else begin
// pick the monster out of so many random ones closest to the level we want
monster := Pick(K.Monsters.Lines);
lev := StrToInt(Split(monster,1));
i := 5;
while (i > 0) do begin // or (lev - level > 4) do begin
m1 := Pick(K.Monsters.Lines);
if abs(level-StrToInt(Split(m1,1))) < abs(level-lev) then begin
monster := m1;
lev := StrToInt(Split(monster,1));
end;
if i > 0 then Dec(i);
end;
end;
fTask.Caption := monster;
Result := Split(monster,0);
fTask.Caption := 'kill|' + fTask.Caption;
qty := 1;
if (level-lev) > 10 then begin
// lev is too low. multiply...
qty := (level + Random(lev)) div max(lev,1);
if qty < 1 then qty := 1;
level := level div qty;
end;
if (level - lev) <= -10 then begin
Result := 'imaginary ' + Result;
end else if (level-lev) < -5 then begin
i := 10+(level-lev);
i := 5-Random(i+1);
Result := Sick(i,Young((lev-level)-i,Result));
end else if ((level-lev) < 0) and (Random(2) = 1) then begin
Result := Sick(level-lev,Result);
end else if ((level-lev) < 0) then begin
Result := Young(level-lev,Result);
end else if (level-lev) >= 10 then begin
Result := 'messianic ' + Result;
end else if (level-lev) > 5 then begin
i := 10-(level-lev);
i := 5-Random(i+1);
Result := Big(i,Special((level-lev)-i,Result));
end else if ((level-lev) > 0) and (Random(2) = 1) then begin
Result := Big(level-lev,Result);
end else if ((level-lev) > 0) then begin
Result := Special(level-lev,Result);
end;
lev := level;
level := lev * qty;
Result := Indefinite(Result, qty);
end;
function ProperCase(s:String):String;
begin
Result := UpperCase(Copy(s,1,1)) + Copy(s,2,10000);
end;
function TMainForm.EquipPrice: Integer;
begin
Result := 5 * GetI(Traits,'Level') * GetI(Traits,'Level')
+ 10 * GetI(Traits,'Level')
+ 20;
end;
procedure TMainForm.Dequeue;
var
s, a, old: String;
n, l: Integer;
begin
while TaskDone do begin
if Split(fTask.Caption,0) = 'kill' then begin
if Split(fTask.Caption,3) = '*' then begin
WinItem;
end else if Split(fTask.Caption,3) <> '' then begin
Add(Inventory,LowerCase(Split(fTask.Caption,1) + ' ' + ProperCase(Split(fTask.Caption,3))),1);
end;
end else if fTask.Caption = 'buying' then begin
// buy some equipment
Add(Inventory,'Gold',-EquipPrice);
WinEquip;
end else if (fTask.Caption = 'market') or (fTask.Caption = 'sell') then with Inventory do begin
if fTask.Caption = 'sell' then begin
Tag := GetI(Inventory,1);
Tag := Tag * GetI(Traits,'Level');
if Pos(' of ',Items[1].Caption) > 0 then
Tag := Tag * (1+RandomLow(10)) * (1+RandomLow(GetI(Traits,'Level')));
Items[0].MakeVisible(false);
Items.Delete(1);
Add(Inventory,'Gold',Tag);
end;
if Items.Count > 1 then begin
Task('Selling ' + Indefinite(Inventory.Items[1].Caption, GetI(Inventory,1)), 1 * 1000);
fTask.Caption := 'sell';
break;
end;
end;
old := fTask.Caption;
fTask.Caption := '';
if (fQueue.Items.Count > 0) then begin
a := Split(fQueue.Items[0],0);
n := StrToInt(Split(fQueue.Items[0],1));
s := Split(fQueue.Items[0],2);
if a = 'task' then begin
Task(s, n * 1000);
fQueue.Items.Delete(0);
end else begin
raise Exception.Create('bah!');
end;
end else with Encumbar do if Position >= Max then begin
Task('Heading to market to sell loot',4 * 1000);
fTask.Caption := 'market';
end else if (Pos('kill|',old) <= 0) and (old <> 'heading') then begin
if GetI(Inventory, 'Gold') > EquipPrice then begin
Task('Negotiating purchase of better equipment', 5 * 1000);
fTask.Caption := 'buying';
end else begin
Task('Heading to the killing fields', 4 * 1000);
fTask.Caption := 'heading';
end;
end else begin
n := GetI(Traits,'Level');
l := n;
s := MonsterTask(n);
n := (2 * InventoryLabelAlsoGameStyle.Tag * n * 1000) div l;
Task('Executing ' + s, n);
end;
end;
end;
function IndexOf(list: TListView; key: String): Integer;
var
i: Integer;
begin
for i := 0 to list.Items.Count-1 do if list.Items.Item[i].Caption = key then begin
Result := i;
Exit;
end;
with list.Items.Add do begin
Result := Index;
Caption := key;
MakeVisible(false);
list.Width := list.Width - 1; // trigger an autosize
end;
end;
procedure TMainForm.Put(list: TListView; key, value: String);
begin
Put(list, IndexOf(list,key), value);
end;
procedure TMainForm.Put(list: TListView; key: String; value: Integer);
begin
Put(list,key,IntToStr(value));
if key = 'STR' then
Encumbar.Max := 10 + value;
if list = Inventory then with Encumbar do begin
Position := Sum(Inventory) - GetI(Inventory,'Gold');
Hint := IntToStr(Position) + '/' + IntToStr(Max) + ' cubits';
end;
end;
procedure TMainForm.Put(list: TListView; pos: Integer; value: String);
begin
with list.Items.Item[pos] do begin
if SubItems.Count < 1
then SubItems.Add(value)
else SubItems[0] := value;
end;
//list.MultiSelect := true;
//list.RowSelect := true;
//list.HideSelection := false;
list.Items[pos].Selected := true;
end;
function LevelUpTime(level: Integer): Integer;
begin
// ~20 minutes for level 1, eventually dominated by exponential
Result := Round((20.0 + IntPower(1.15,level)) * 60.0);
end;
procedure TMainForm.GoButtonClick(Sender: TObject);
begin
with ExpBar do begin
Position := 0;
Max := LevelUpTime(1);
end;
fTask.Caption := '';
fQuest.Caption := '';
fQueue.Items.Clear;
Task('Loading.',2000); // that dot is spotted for later...
Q('task|10|Experiencing an enigmatic and foreboding night vision');
Q('task|6|Much is revealed about that wise old bastard you''d underestimated');
Q('task|6|A shocking series of events leaves you alone and bewildered, but resolute');
Q('task|4|Drawing upon an unexpected reserve of determination, you set out on a long and dangerous journey');
Q('task|2|Loading');
PlotBar.Max := 26;
with Plots.Items.Add do begin
Caption := 'Prologue';
StateIndex := 0;
end;
StartTimer;
SaveGame;
Brag('s');
end;
procedure TMainForm.WinSpell;
begin
AddR(Spells, K.Spells.Lines[RandomLow(Min(GetI(Stats,'WIS')+GetI(Traits,'Level'),
K.Spells.Lines.Count))], 1);
end;
function LPick(list: TStrings; goal: Integer): String;
var
i, best, b1: Integer;
s: String;
begin
Result := Pick(list);
for i := 1 to 5 do begin
best := StrToInt(Split(Result,1));
s := Pick(list);
b1 := StrToInt(Split(s,1));
if abs(goal-best) > abs(goal-b1) then
Result := s;
end;
end;
procedure TMainForm.WinEquip;
var
posn, qual, plus, count: Integer;
name, modifier: String;
stuff, better, worse: TStrings;
begin
posn := Random(Equips.Items.Count);
Equips.Tag := posn; // remember as the "best item"
if posn = 0 then begin
stuff := K.Weapons.Lines;
better := K.OffenseAttrib.Lines;
worse := K.OffenseBad.Lines;
end else begin
better := K.DefenseAttrib.Lines;
worse := K.DefenseBad.Lines;
if posn = 1
then stuff := K.Shields.Lines
else stuff := K.Armors.Lines;
end;
name := LPick(stuff,GetI(Traits,'Level'));
qual := StrToInt(Split(name,1));
name := Split(name,0);
plus := GetI(Traits,'Level') - qual;
if plus < 0 then better := worse;
count := 0;
while (count < 2) and (plus <> 0) do begin
modifier := Pick(better);
qual := StrToInt(Split(modifier, 1));
modifier := Split(modifier, 0);
if Pos(modifier, name) > 0 then Break; // no repeats
if Abs(plus) < Abs(qual) then Break; // too much
name := modifier + ' ' + name;
Dec(plus, qual);
Inc(count);
end;
if plus <> 0 then name := IntToStr(plus) + ' ' + name;
if plus > 0 then name := '+' + name;
Put(Equips, posn, name);
end;
procedure TMainForm.WinStat;
var
i,t: Integer;
function Square(x: Integer): Integer; begin Result := x * x; end;
begin
if Odds(1,2)
then i := Random(Stats.Items.Count)
else begin
// favor the best stat so it will tend to clump
t := 0;
for i := 0 to 5 do Inc(t,Square(GetI(Stats,i)));
t := Random(t);
i := -1;
while t >= 0 do begin
Inc(i);
Dec(t,Square(GetI(Stats,i)));
end;
end;
Add(Stats, Stats.Items[i].Caption, 1);
end;
function TMainForm.SpecialItem: String;
begin
Result := InterestingItem + ' of ' +
Pick(K.ItemOfs.Lines);
end;
function TMainForm.InterestingItem: String;
begin
Result := Pick(K.ItemAttrib.Lines) + ' ' +
Pick(K.Specials.Lines);
end;
function TMainForm.BoringItem: String;
begin
Result := Pick(K.BoringItems.Lines);
end;
procedure TMainForm.WinItem;
begin
Add(Inventory, SpecialItem, 1);
end;
procedure TMainForm.CompleteQuest;
var
lev, level, l, i, montag: Integer;
m: string;
begin
lev := 0; // Quell stupid compiler warning
with QuestBar do begin
Position := 0;
Max := 50 + Random(100);
end;
with Quests do begin
if Items.Count > 0 then begin
Log('Quest completed: ' + Items[Items.Count-1].Caption);
Items[Items.Count-1].StateIndex := 1;
case Random(4) of
0: WinSpell;
1: WinEquip;
2: WinStat;
3: WinItem;
end;
end;
while Items.Count > 99 do Items.Delete(0);
with Items.Add do begin
case Random(5) of
0: begin
level := GetI(Traits,'Level');
for i := 1 to 4 do begin
montag := Random(K.Monsters.Lines.Count);
m := K.Monsters.Lines[montag];
l := StrToInt(Split(m,1));
if (i = 1) or (abs(l - level) < abs(lev - level)) then begin
lev := l;
fQuest.Caption := m;
fQuest.Tag := montag;
end;
end;
Caption := 'Exterminate ' + Definite(Split(fQuest.Caption,0),2);
end;
1: begin
fQuest.Caption := InterestingItem;
Caption := 'Seek ' + Definite(fQuest.Caption,1);
fQuest.Caption := '';
end;
2: begin
fQuest.Caption := BoringItem;
Caption := 'Deliver this ' + fQuest.Caption;
fQuest.Caption := '';
end;
3: begin
fQuest.Caption := BoringItem;
Caption := 'Fetch me ' + Indefinite(fQuest.Caption,1);
fQuest.Caption := '';
end;
4: begin
level := GetI(Traits,'Level');
for i := 1 to 2 do begin
montag := Random(K.Monsters.Lines.Count);
m := K.Monsters.Lines[montag];
l := StrToInt(Split(m,1));
if (i = 1) or (abs(l - level) < abs(lev - level)) then begin
lev := l;
fQuest.Caption := m;
end;
end;
Caption := 'Placate ' + Definite(Split(fQuest.Caption,0),2);
fQuest.Caption := '';
end;
end;
Log('Commencing quest: ' + Caption);
StateIndex := 0;
MakeVisible(false);
end;
Width := Width - 1; // trigger a column resize
end;
SaveGame;
end;
function Rome(var n: Integer; dn: Integer; var s: String; ds: String): Boolean;
begin
Result := (n >= dn);
if Result then begin
n := n - dn;
s := s + ds;
end;
end;
function UnRome(var s: String; dn: Integer; var n: Integer; ds: String): Boolean;
begin
Result := (Copy(s,1,Length(ds)) = ds);
if Result then begin
s := Copy(s,Length(ds)+1,10000);
n := n + dn;
end;
end;
function IntToRoman(n: Integer): String;
begin
while Rome(n, 1000, Result, 'M') do ;
Rome(n, 900, Result, 'CM');
Rome(n, 500, Result, 'D');
Rome(n, 400, Result, 'CD');
while Rome(n, 100, Result, 'C') do ;
Rome(n, 90, Result, 'XC');
Rome(n, 50, Result, 'L');
Rome(n, 40, Result, 'XL');
while Rome(n, 10, Result, 'X') do ;
Rome(n, 9, Result, 'IX');
Rome(n, 5, Result, 'V');
Rome(n, 4, Result, 'IV');
while Rome(n, 1, Result, 'I') do ;
end;
function RomanToInt(n: String): Integer;
begin
Result := 0;
while UnRome(n, 1000, Result, 'M') do ;
UnRome(n, 900, Result, 'CM');
UnRome(n, 500, Result, 'D');
UnRome(n, 400, Result, 'CD');
while UnRome(n, 100, Result, 'C') do ;
UnRome(n, 90, Result, 'XC');
UnRome(n, 50, Result, 'L');
UnRome(n, 40, Result, 'XL');
while UnRome(n, 10, Result, 'X') do ;
UnRome(n, 9, Result, 'IX');
UnRome(n, 5, Result, 'V');
UnRome(n, 4, Result, 'IV');
while UnRome(n, 1, Result, 'I') do ;
end;
procedure TMainForm.CompleteAct;
begin
PlotBar.Position := 0;
with Plots do begin
Items[Items.Count-1].StateIndex := 1;
PlotBar.Max := 60 * 60 * (1 + 5 * Items.Count); // 1 hr + 5/act
PlotBar.Hint := 'Cutscene omitted';
with Items.Add do begin
Caption := 'Act ' + IntToRoman(Items.Count-1);
MakeVisible(false);
StateIndex := 0;
Width := Width-1;
end;
end;
SaveGame;
Brag('a');
end;
procedure TMainForm.Log(line: String);
var
stamp: String;
logname: String;
log: TextFile;
begin
if FLogEvents then begin
logname := ChangeFileExt(GameSaveName, '.log');
DateTimeToString(stamp, '[yyyy-mm-dd hh:nn:ss]', Now);
AssignFile(log, logname);
if FileExists(logname) then Append(log) else Rewrite(log);
WriteLn(log, stamp + ' ' + line);
Flush(log);
CloseFile(log);
end;
end;
procedure TMainForm.ExportCharSheet;
var
f: TextFile;
i: Integer;
begin
AssignFile(f, ChangeFileExt(GameSaveName, '.sheet'));
Rewrite(f);
Write(f,Get(Traits,'Name'));
if GetHostName <> '' then
Write(f,' [' + GetHostName + ']');
WriteLn(f);
WriteLn(f,Get(Traits,'Race') + ' ' + Get(Traits,'Class'));
WriteLn(f,Format('Level %d (exp. %d/%d)', [GetI(Traits,'Level'), ExpBar.Position, ExpBar.Max]));
//WriteLn(f,'Level ' + Get(Traits,'Level') + ' (' + ExpBar.Hint + ')');
WriteLn(f);
with Plots do if Items.Count > 0 then
WriteLn(f,'Plot stage: ' + Items[Items.Count-1].Caption + ' (' + PlotBar.Hint + ')');
with Quests do if Items.Count > 0 then
WriteLn(f,'Quest: ' + Items[Items.Count-1].Caption + ' (' + QuestBar.Hint + ')');
WriteLn(f);
WriteLn(f, 'Stats:');
WriteLn(f, Format(' STR%7d', [GetI(Stats,'STR')]));
WriteLn(f, Format(' CON%7d', [GetI(Stats,'CON')]));
WriteLn(f, Format(' DEX%7d', [GetI(Stats,'DEX')]));
WriteLn(f, Format(' INT%7d', [GetI(Stats,'INT')]));
WriteLn(f, Format(' WIS%7d HP Max%7d', [GetI(Stats,'WIS'), GetI(Stats,'HP Max')]));
WriteLn(f, Format(' CHA%7d MP Max%7d', [GetI(Stats,'CHA'), GetI(Stats,'MP Max')]));
WriteLn(f);
WriteLn(f, 'Equipment:');
for i := 1 to Equips.Items.Count-1 do
if Get(Equips,i) <> '' then
WriteLn(f, ' ' + LeftStr(Equips.Items[i].Caption + ' ', 12) + Get(Equips,i));
WriteLn(f);
WriteLn(f, 'Spell Book:');
with Spells do
for i := 1 to Items.Count-1 do
WriteLn(f, ' ' + Items[i].Caption + ' ' + Get(Spells,i));
WriteLn(f);
WriteLn(f, 'Inventory (' + EncumBar.Hint + '):');
WriteLn(f, ' ' + Indefinite('gold piece', GetI(Inventory, 'Gold')));
with Inventory do
for i := 2 to Items.Count-1 do
if Pos(' of ', Items[i].Caption) > 0
then WriteLn(f, ' ' + Definite(Items[i].Caption, GetI(Inventory,i)))
else WriteLn(f, ' ' + Indefinite(Items[i].Caption, GetI(Inventory,i)));
WriteLn(f);
WriteLn(f, '-- ' + DateTimeToStr(Now));
WriteLn(f, '-- Progress Quest 6.2 - http://progressquest.com/');
Flush(f);
CloseFile(f);
end;
procedure TMainForm.Task(caption: String; msec: Integer);
begin
Kill.SimpleText := caption + '...';
Log(Kill.SimpleText);
with TaskBar do begin
Position := 0;
Max := msec;
end;
end;
procedure TMainForm.Add(list: TListView; key: String; value: Integer);
var line: String;
begin
Put(list, key, value + GetI(list,key));
if value = 0 then Exit;
if value > 0 then line := 'Gained' else line := 'Lost';
if key = 'Gold' then begin
key := 'gold piece';
if value > 0 then line := 'Got paid' else line := 'Spent';
end;
if value < 0 then value := -value;
line := line + ' ' + Indefinite(key, value);
Log(line);
end;
procedure TMainForm.AddR(list: TListView; key: String; value: Integer);
begin
Put(list, key, IntToRoman(value + RomanToInt(Get(list,key))));
end;
function TMainForm.Get(list: TListView; key: String): String;
begin
Result := Get(list, IndexOf(list,key));
end;
function TMainForm.Get(list: TListView; index: Integer): String;
begin
with list.Items.Item[index] do begin
if SubItems.Count < 1
then Result := ''
else Result := SubItems[0];
end;
end;
function TMainForm.GetI(list: TListView; key: String): Integer;
begin
Result := StrToIntDef(Get(list,key),0);
end;
function TMainForm.GetI(list: TListView; index: Integer): Integer;
begin
Result := StrToIntDef(Get(list,index),0);
end;
function TMainForm.Sum(list: TListView): Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to list.Items.Count - 1 do
Inc(Result, GetI(list,i));
end;
procedure PutLast(list: TListView; value: String);
begin
if list.Items.Count > 0 then
with list.Items.Item[list.Items.Count-1] do begin
if SubItems.Count < 1
then SubItems.Add(value)
else SubItems[0] := value;
end;
list.Width := list.Width - 1; // trigger an autosize
end;
procedure TMainForm.LevelUp;
var
i: Integer;
begin
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));
for i := 1 to 2 do WinStat;
WinSpell;
with ExpBar do begin
Position := 0;
Max := LevelUpTime(GetI(Traits,'Level'));
end;
SaveGame;
Brag('l');
end;
procedure TMainForm.ClearAllSelections;
begin
Equips.ClearSelection;
Spells.ClearSelection;
Stats.ClearSelection;
Traits.ClearSelection;
Inventory.ClearSelection;
Plots.ClearSelection;
Quests.ClearSelection;
end;
function RoughTime(s: Integer): String;
begin
if s < 120 then Result := IntToStr(s) + ' seconds'
else if s < 60 * 120 then Result := IntToStr(s div 60) + ' minutes'
else if s < 60 * 60 * 48 then Result := IntToStr(s div 3600) + ' hours'
else Result := IntToStr(s div (3600 * 24)) + ' days';
end;
procedure TMainForm.Timer1Timer(Sender: TObject);
var
gain: Boolean;
elapsed: Integer;
begin
gain := Pos('kill|',fTask.Caption) = 1;
with TaskBar do begin
if Position >= Max then begin
ClearAllSelections;
if Kill.SimpleText = 'Loading....' then Max := 0;
if gain then with ExpBar do if Position >= Max
then LevelUp
else Position := Position + TaskBar.Max div 1000;
with ExpBar do Hint := IntToStr(Max-Position) + ' XP needed for next level';
if gain then if Plots.Items.Count > 1 then with QuestBar do if Position >= Max then begin
CompleteQuest;
end else if Quests.Items.Count > 0 then begin
Position := Position + TaskBar.Max div 1000;
Hint := IntToStr(100 * Position div Max) + '% complete';
end;
with PlotBar do if Position >= Max
then CompleteAct
else Position := Position + TaskBar.Max div 1000;
//Time.Caption := FormatDateTime('h:mm:ss',PlotBar.Position / (24.0 * 60 * 60));
PlotBar.Hint := RoughTime(PlotBar.Max-PlotBar.Position) + ' remaining';
//PlotBar.Hint := FormatDateTime('h:mm:ss" remaining"',(PlotBar.Max-PlotBar.Position) / (24.0 * 60 * 60));
Dequeue();
end else with TaskBar do begin
elapsed := LongInt(timeGetTime) - LongInt(Timer1.Tag);
if elapsed > 100 then elapsed := 100;
if elapsed < 0 then elapsed := 0;
Position := Position + elapsed;//Integer(Timer1.Interval);
end;
end;
Timer1.Tag := timeGetTime;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
QuestBar.Position := 0;
PlotBar.Position := 0;
TaskBar.Position := 0;
ExpBar.Position := 0;
Encumbar.Position := 0;
FReportSave := true;
FLogEvents := false;
FMakeBackups := false;
FMinToTray := true;
FExportSheets := false;
MakeFileAssociations;
end;
procedure TMainForm.SpeedButton1Click(Sender: TObject);
begin
{$IFDEF CHEATS}
TaskBar.Position := TaskBar.Max;
{$ENDIF}
end;
function TMainForm.RollCharacter: Boolean;
var
f: Integer;
begin
Result := true;
repeat
if not NewGuyForm.Go then begin
Result := false;
Exit;
end;
Put(Traits, 'Name', NewGuyForm.Name.Text);
if FileExists(GameSaveName) and
(mrNo = MessageDlg('The saved game "' + GameSaveName + '" already exists. Do you want to overwrite it?', mtWarning, [mbYes,mbNo], 0)) then begin
// go around again
end else begin
f := FileCreate(GameSaveName);
if f = -1 then begin
ShowMessage('The thought police don''t like the name "' + GameSaveName + '". Choose a name without \\ / : * ? " < > or | in it.');
end else begin
FileClose(f);
Break;
end;
end;
until false;
with NewGuyForm do begin
Put(Traits,'Name',Name.Text);
Put(Traits,'Race',Race.Items[Race.ItemIndex]);
Put(Traits,'Class',Klass.Items[Klass.ItemIndex]);
Put(Traits,'Level',1);
Put(Stats,'STR',STR.Tag);
Put(Stats,'CON',CON.Tag);
Put(Stats,'DEX',DEX.Tag);
Put(Stats,'INT',INT.Tag);
Put(Stats,'WIS',WIS.Tag);
Put(Stats,'CHA',CHA.Tag);
Put(Stats,'HP Max',Random(8) + CON.Tag div 6);
Put(Stats,'MP Max',Random(8) + INT.Tag div 6);
Put(Equips,'Weapon','Sharp Stick');
Put(Inventory,'Gold',0);
InventoryLabelAlsoGameStyle.Tag := 3;//GameStyle.Position;
ClearAllSelections;
GoButtonClick(NewGuyForm);
end;
end;
procedure TMainForm.FormShow(Sender: TObject);
var
done, exportandexit: Boolean;
i: Integer;
begin
if Timer1.Enabled then Exit;
done := false;
exportandexit := false;
for i := 1 to ParamCount do begin
if ParamStr(i) = '-log'
then FLogEvents := true
else if ParamStr(i) = '-backup'
then FMakeBackups := true
else if ParamStr(i) = '-no-report-save'
then FReportSave := false
else if ParamStr(i) = '-no-tray'
then FMinToTray := false
else if ParamStr(i) = '-export'
then FExportSheets := true
else if ParamStr(i) = '-export-only'
then exportandexit := true
else begin
LoadGame(ParamStr(i));
if exportandexit then begin
ExportCharSheet;
Timer1.Enabled := false;
Close;
end;
Exit;
end;
end;
while not Done do begin
SetHostName('');
SetHostAddr('');
SetLogin('');
SetPassword('');
case FrontForm.ShowModal of
mrOk: begin
done := RollCharacter;
end;
mrRetry: begin
// load
if FrontForm.OpenDialog1.Execute then begin
LoadGame(FrontForm.OpenDialog1.Filename);
Done := true;
end;
end;
mrYesToAll: begin
Done := ServerSelectForm.Go;
end;
mrCancel: begin
Close;
Done := true;
end;
end;
end;
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
{$IFDEF CHEATS}
LevelUp;
{$ENDIF}
end;
procedure TMainForm.CashInClick(Sender: TObject);
begin
{$IFDEF CHEATS}
WinEquip;
WinItem;
WinSpell;
WinStat;
Add(Inventory,'Gold',Random(100));
{$ENDIF}
end;
procedure TMainForm.FinishQuestClick(Sender: TObject);
begin
{$IFDEF CHEATS}
QuestBar.Position := QuestBar.Max;
TaskBar.Position := TaskBar.Max;
{$ENDIF}
end;
procedure TMainForm.CheatPlotClick(Sender: TObject);
begin
{$IFDEF CHEATS}
PlotBar.Position := PlotBar.Max;
TaskBar.Position := TaskBar.Max;
{$ENDIF}
end;
function TMainForm.SaveGame: Boolean;
var
f: TFileStream;
m: TMemoryStream;
i: Integer;
begin
Log('Saving game: ' + GameSaveName);
Result := true;
try
if FMakeBackups then begin
DeleteFile(ChangeFileExt(GameSaveName, '.bak'));
MoveFile(PChar(GameSaveName), PChar(ChangeFileExt(GameSaveName, '.bak')));
end;
f := TFileStream.Create(GameSaveName, fmCreate);
except
on EfCreateError do begin
Result := false;
Exit;
end;
end;
//ClearAllSelections;
m := TMemoryStream.Create;
for i := 0 to ComponentCount-1 do
m.WriteComponent(Components[i]);
m.Seek(0, soFromBeginning);
ZCompressStream(m, f);
m.Free;
f.Free;
end;
procedure TMainForm.LoadGame(name: String);
var
f: TStream;
m: TStream;
i: Integer;
begin
FSaveFileName := name;
m := TMemoryStream.Create;
f := TFileStream.Create(name, fmOpenRead);
try
ZDecompressStream(f, m);
f.Free;
except
on EZCompressionError do begin
// backwards-compatibility
//m.Free;
//m := f;
//f := nil;
ShowMessage('Error loading game.');
Close;
Exit;
end;
end;
Traits.Items.Clear;
Stats.Items.Clear;
Equips.Items.Clear;
m.Seek(0, soFromBeginning);
for i := 0 to ComponentCount-1 do
m.ReadComponent(Components[i]);
m.Free;
Log('Loaded game: ' + name);
StartTimer;
TriggerAutosizes;
end;
procedure TMainForm.TriggerAutosizes;
begin
Plots.Width := 100;
Quests.Width := 100;
Inventory.Width := 100;
Equips.Width := 100;
Spells.Width := 100;
Traits.Width := 100;
Stats.Width := 100;
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Timer1.Enabled then begin
Timer1.Enabled := false;
Shell_NotifyIcon(NIM_DELETE, @FTrayIcon);
if SaveGame then
if FReportSave then
ShowMessage('Game saved as ' + GameSaveName);
end;
FReportSave := true;
Action := caFree;
end;
function TMainForm.GameSaveName: String;
begin
if FSaveFileName = '' then begin
FSaveFileName := Get(Traits,'Name');
if GetHostName <> '' then
FSaveFileName := FSaveFileName + ' [' + GetHostName + ']';
FSaveFileName := FSaveFileName + kFileExt;
FSaveFileName := ExpandFileName(PChar(FSaveFileName));
end;
Result := FSaveFileName;
end;
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (FindWindow('TAppBuilder', nil) > 0) and (ssCtrl in Shift) and (ssShift in Shift) and (Key = ord('C')) then begin
{$IFDEF CHEATS}
Cheats.Visible := not Cheats.Visible;
{$ENDIF}
end;
if GetPasskey = 0 then Exit; // no need for these things
if (ssCtrl in Shift) and (Key = ord('B')) then begin
Brag('b');
Navigate(GetHostAddr + 'name=' + UrlEncode(Get(Traits,'Name')));
end;
if (ssCtrl in Shift) and (Key = ord('M')) then begin
SetMotto(InputBox('Progress Quest', 'Declare your motto!', GetMotto));
Brag('m');
Navigate(GetHostAddr + 'name=' + UrlEncode(Get(Traits,'Name')));
end;
if (ssCtrl in Shift) and (Key = ord('G')) then begin
SetGuild(InputBox('Progress Quest', 'Choose a guild.'#13#13'Make sure you undestand the guild rules before you join one. To learn more about guilds, visit http://progressquest.com/guilds.php', GetGuild));
Guildify;
end;
end;
procedure Navigate(url: String);
begin
ShellExecute(GetDesktopWindow(), 'open', PChar(url),
nil, '', SW_SHOW);
end;
function LFSR(pt: String; salt: Integer): Integer;
var
k: Integer;
begin
Result := salt;
for k := 1 to Length(pt) do
Result := Ord(pt[k])
xor (Result shl 1)
xor (1 and ((Result shr 31) xor (Result shr 5)));
for k := 1 to 10 do
Result := (Result shl 1)
xor (1 and ((Result shr 31) xor (Result shr 5)));
end;
procedure TMainForm.Brag(trigger: String);
var
url, body: string;
best, i: Integer;
const
flat = 1;
begin
if FExportSheets then
ExportCharSheet;
if GetPasskey = 0 then Exit; // not a online game!
url := 'cmd=b&t=' + trigger;
with Traits do for i := 0 to Items.Count-1 do
url := url + '&' + LowerCase(Items[i].Caption[1]) + '=' + UrlEncode(Items[i].Subitems[0]);
url := url + '&x=' + IntToStr(ExpBar.Position);
url := url + '&i=' + UrlEncode(Get(Equips,Equips.Tag));
if Equips.Tag > 1 then url := url + '+' + Equips.Items[Equips.Tag].Caption;
best := 0;
if Spells.Items.Count > 0 then with Spells do begin
for i := 1 to Items.Count-1 do
if (i+flat) * RomanToInt(Get(Spells,i)) >
(best+flat) * RomanToInt(Get(Spells,best)) then
best := i;
url := url + '&z=' + UrlEncode(Items[best].Caption + ' ' + Get(Spells,best));
end;
best := 0;
for i := 1 to 5 do
if GetI(Stats,i) > GetI(Stats,best) then best := i;
url := url + '&k=' + Stats.Items[best].Caption + '+' + Get(Stats,best);
url := url + '&a=' + UrlEncode(Plots.Items[Plots.Items.Count-1].Caption);
url := url + '&h=' + UrlEncode(GetHostName);
url := url + RevString;
url := url + '&p=' + IntToStr(LFSR(url, GetPasskey));
url := url + '&m=' + UrlEncode(GetMotto);
url := AuthenticateUrl(GetHostAddr + url);
try
body := DownloadString(url);
if (LowerCase(Split(body,0)) = 'report') then
ShowMessage(Split(body,1));
except
on EWebError do begin
// 'ats okay.
end;
end;
end;
function TMainForm.AuthenticateUrl(url: String): String;
begin
if (GetLogin <> '') or (GetPassword <> '') then
Result := StuffString(url, 8, 0, GetLogin+':'+GetPassword+'@')
else
Result := url;
end;
procedure TMainForm.Guildify;
var
url, s,b: string;
i: Integer;
begin
if GetPasskey = 0 then Exit; // not a online game!
url := 'cmd=guild';
with Traits do for i := 0 to Items.Count-1 do
url := url + '&' + LowerCase(Items[i].Caption[1]) + '=' + UrlEncode(Items[i].Subitems[0]);
url := url + '&h=' + UrlEncode(GetHostName);
url := url + RevString;
url := url + '&guild=' + UrlEncode(GetGuild);
url := url + '&p=' + IntToStr(LFSR(url, GetPasskey));
url := AuthenticateUrl(GetHostAddr + url);
try
b := DownloadString(url);
s := Take(b);
if s <> '' then ShowMessage(s);
s := Take(b);
if s <> '' then Navigate(s);
except
on EWebError do begin
// 'ats okay.
Abort;
end;
end;
end;
procedure TMainForm.OnQueryEndSession(var Msg: TMessage);
var Action: TCloseAction;
begin
FReportSave := false;
FormClose(Self, Action);
ReplyMessage(-1);
end;
procedure TMainForm.OnEndSession(var Msg: TMessage);
var Action: TCloseAction;
begin
Msg.Result := 0;
if Msg.wParam <> 0 then begin
FReportSave := false;
FormClose(Self, Action);
end;
ReplyMessage(0);
end;
initialization
RegisterClasses([TMainForm]);
end.