Core Skeleton Example
An example program with save game features, creating a new player, loading the player, and maintenance routines.
What does the Player Record look like?
How do you handle loading the player?
How do you run maintenance for the game?
How do you create a new player?
How do you set the game up with saving?
What does the Player Record look like?
All global variables are kept in VAR.PAS. All the people playing the game are stored in TotalData(TP). The current player in-game is PlayerData (CP). CP is loaded from TP when the game starts, and stores all the integer information for the current character. TP will store 50 players in total.
unit vars;
interface
const
ect...
type
TotalData = record
handle: string[64];
name: string[16];
turns: string[2];
ect...
end;
PlayerData = record
handle: string[64];
name: string[16];
turns: integer;
ect...
end;
var
ect...
TP: array[1..50] of TotalData;
CP: PlayerData;
How do you handle loading the player?
All the load routines are located in LOADRTN.PAS, loading the player, monsters, and game config information. loadTP loads all the players into the TP record - a global that stores all character information. This routine returns the total players loaded:
function loadTP:integer;
var
ect...
begin
{ clear the array for player REMOVE }
pIndex:=1;
while pIndex <= 50 do
begin
TP[pIndex].handle:= '';
TP[pIndex].name:= '';
ect...
inc(pIndex);
end;
{ load TP: Total Players from the Player File }
gIndex:=1;
pIndex:=1;
assign(P,'SKEL.PLR');
reset(P);
while not EOF(P) do
begin
readln(P,s);
if s<>'' then
begin
last:=0;
pos:=last+1;
while (pos<=length(s)) and (s[pos]<>',') do
inc(pos);
TP[pIndex].handle := copy(s,last+1,(pos-last-1));
last:=pos;
pos:=last+1;
while (pos<=length(s)) and (s[pos]<>',') do
inc(pos);
TP[pIndex].name := copy(s,last+1,(pos-last-1));
inc(pIndex);
end;
end;
{ Set Global Index for Total Players }
gIndex:= pIndex-1;
close(P);
{ Return Total Loaded }
loadTP:=pIndex;
end;
The player file (SKL.PLR) is comma delimited. This routine starts grabbing strings between the commas. Later, we'll convert those strings into integers.
How do you run maintenance for the game?
The maintenance routine is in MNTCTRL.PAS. checkMaintenance is called when the game is run. It reads the last time maintenance was run by pulling the date from the config file (SKL.CFG). Then, compare it against today's date.
procedure checkMaintenance;
var
ect...
begin
assign(F,'SKEL.CFG');
reset(F);
readln(F,s);
close(F);
date:= Copy(s,0,8);
ect...
Val(date,dateVal,code);
GetDate(Year,Month,Day,Hour);
currentDate := L0(Year)+L0(Month)+L0(Day);
Val(currentDate,currentDateVal,code);
if currentDateVal > dateVal then
runMaintenance(currentDate,turns,days,pBattle,moonPhaseString,GV.topPlayer);
end;
If today's date is greater than the value stored, runMaintenance is executed. This compares the current date against the player's last log-on date. Going through all the players in the game.
procedure runMaintenance(currentDate,turns,days,pBattle,moonPhaseString,topPlayer:string);
var
ect...
begin
ect...
for i:=1 to pIndex-1 do
begin
days_total:= 0;
pYearString:= copy(TP[i].totaldays,0,2);
pMonthString:= copy(TP[i].totaldays,3,2);
pDayString:= copy(TP[i].totaldays,5,2);
{ Player Date }
Val(pDayString,pDay,code);
Val(pMonthString,pMonth,code);
Val(pYearString,pYear,code);
{ Calculates Current Time against Players Last Login }
if cYear = pYear then
begin
if cMonth = pMonth then
begin
days_total:= cDay-pDay;
end
else
begin
for fdi:= pMonth+1 to cMonth-1 do
begin
days_total:= days_total + month_days[fdi];
end;
days_total:= days_total + (cDay+(month_days[pMonth]-pDay));
end;
end
else
begin
for fdi:= 1 to cMonth-1 do
begin
days_total:= days_total+month_days[fdi];
end;
for fdi:= pMonth+1 to 12 do
begin
days_total:= days_total+month_days[fdi];
end;
if cYear - pYear >= 0 then
begin
days_total:= days_total + ((cYear - pYear - 1)*365) +
(month_days[pMonth] - pDay + cDay);
end;
end;
if TP[i].totaldays<>'REMOVE' then
begin
{ If The Player Hasn't Been On In X Days:(SKEL.CFG) REMOVE }
Val(days,daysVal,code);
if days_total >= daysVal then TP[i].totaldays:= 'REMOVE';
end;
end; { end For statement }
ect...
end;
Next, we have to resurrect any character who died the day before. The status value is checked. This value stores the death date of the player. So if it's greater than 10, the player has died. Below 10 is used to know if the player is alive.
{ Clears Death If Player Died The Day Before }
Val(returnShortDate,tempDate,code);
for i:=1 to pIndex-1 do
begin
{ Restore Special Attack Points To Max }
TP[i].spattack:=TP[i].spmax;
Val(TP[i].status,tempStatus,code);
if tempStatus > 10 then
begin
{ Set the status to Rezzed so the game knows rez player on log in }
Str(deathRezzed,outputString);
if tempStatus < tempDate then TP[i].deathType:= outputString;
end;
end;
Before we can close out maintenance, we need to write the player file back to disk. But we skip over the player that has REMOVE in the totaldays element.
{ Write out Player Save File for characters that are not REMOVED }
assign(P,'SKEL.PLR');
rewrite(P);
for i:=1 to pIndex-1 do
begin
{ Heals the player during maintenance }
TP[i].hitpoints:=TP[i].maxhp;
if TP[i].totaldays <> 'REMOVE' then
writeln(P,TP[i].handle+','
+TP[i].name+','
{ not the same as COMMFUN }
{ directly writes turns & pbattles to the player save file }
+turns+','
+pBattle+','
+TP[i].totaldays+','
ect...
+TP[i].invPotion+',999');
end;
close(P);
How do you create a new player?
CRTENEW.PAS handles creating a new player. At the core of it, you need to test if the name has already been taken. The first part happens here in CRTENEW.PAS.
begin
swrite('Enter your character''s name: ');
sgoto_xy(37,12);
set_color(GV.colorSpeech,0);
sread(newName);
ect...
if testInput = true then
begin
testName:= checkName(newName);
if testName=true then
begin
correctName:= true;
end
else
begin
sgoto_xy(8,16);
set_color(9,0);
swriteln('Name already taken.');
sgoto_xy(28,16);
sread_char(Ch);
end
end
else
begin
ect...
end;
end;
The second test happens in COMMFUN.PAS under checkName. But the names are converted to uppercase before testing.
function convertUpperCase(pName: string): string;
var
l: integer;
pos: integer;
tempCh: char;
outputString: string;
upperString: string;
begin
l:= length(pName);
pos:= 1;
upperString:= '';
while pos <= l do
begin
tempCh:= UpCase(pName[pos]);
insert(tempCh,upperString,pos);
inc(pos);
end;
convertUpperCase:= upperString;
end;
function checkName(newName: string): boolean;
var
i: integer;
outputString: string;
begin
i:= 1;
checkName:= true;
while i <= gIndex do
begin
Str(i,outputString);
if convertUpperCase(newName) = convertUpperCase(TP[i].name) then checkName:= false;
inc(i);
end;
end;
How do you set the game up with saving?
SKELETON.PAS starts off with the pointer to the saveGame routine:
var
ect...
begin;
ect...
saveexitproc:= exitproc;
exitproc:= @myexit1;
Which will execute ERROLOG.PAS running the MyExit1 routine that calls TrapExit:
{$F+} Procedure MyExit1; {$F-}
VAR SaveExitProc: POINTER;
Begin;
TrapExit;
SaveExitProc:=Exitproc;
End;
In ERROLOG.PAS, it calls TrapExit which calls the saveGame routine.
PROCEDURE TrapExit;
CONST
{Replace Skeleton Game Framework with the name of your program}
ProductName='Skeleton Game Framework';
ect...
{Replace the next line with the name of YOUR save procedure}
{This ones save my game information should something go wrong}
{I have left it so you see what I did, although it IS commented out}
{SaveGame(Player,PlayerFile,TempP,Country,CountryFile,Map1,MapFile);}
saveGame;
saveGame routine from COMMFUN.PAS:
procedure saveGame;
var
F: text;
i: integer;
outputString: string;
begin
if charLoc<>0 then
begin
{ Copy the Current Player back into the Total Player record }
TP[charLoc].handle:= CP.handle;
TP[charLoc].name:= CP.name;
Str(CP.turns,outputString);
TP[charLoc].turns:= outputString;
Str(CP.pbattle,outputString);
TP[charLoc].pbattle:= outputString;
{ Store current date on logoff. Used in Maintenance }
TP[charLoc].totaldays:= returnShortDate;
ect...
Str(CP.invPotion,outputString);
TP[charLoc].invPotion:= outputString;
assign(F,'SKEL.PLR');
rewrite(F);
for i:=1 to gIndex do
begin
writeln(F,TP[i].handle+','
+TP[i].name+','
+TP[i].turns+','
+TP[i].pBattle+','
+TP[i].totaldays+','
+TP[i].sex+','
ect...
+TP[i].invPotion+',999');
end;
close(F);
end;
end;
Upon exit, the shortdate is stored in TP[].totaldays. This is checked during maintenance, and the character is deleted if the date is over 180 days. This value is set in SKL.CFG.