Can this be done - game in chapters
bwatford
27 Mar 2007, 12:18Me and witch wyzwurd has been kicking this back and forth for a few days and we are kinda stuck.
What I want to do is to be able to release my really huge game in chapters. That way players don't have to wait for ever and the wait time between chapters is not too extreme.
We have figured out how to make the first chapter right out a code that would be required to be input by the player for the next chapter to start. I even have figured out how to carry over there weapon, score, health, etc. from one chapter to the next using the codes.
But that would allow for players to share the codes and play chapters that the other may not have played the previous etc. and not what I intended.
Is there a way to have the first chapter output a save like file with the codes in it that the player wouldn't know what it does and then require the next chapter to read that file to see if it is legit and to carry over starting gold, items, weapon etc. so they can continue the saga.
Any input is appreciated.
What I want to do is to be able to release my really huge game in chapters. That way players don't have to wait for ever and the wait time between chapters is not too extreme.
We have figured out how to make the first chapter right out a code that would be required to be input by the player for the next chapter to start. I even have figured out how to carry over there weapon, score, health, etc. from one chapter to the next using the codes.
But that would allow for players to share the codes and play chapters that the other may not have played the previous etc. and not what I intended.
Is there a way to have the first chapter output a save like file with the codes in it that the player wouldn't know what it does and then require the next chapter to read that file to see if it is legit and to carry over starting gold, items, weapon etc. so they can continue the saga.
Any input is appreciated.
paul_one
27 Mar 2007, 12:27You can use one save file to open up another game... As long as that game filename is the same (you kind of overwrite the game).
I#'m not sure how _safe_ that is though, as I'm not too sure about the qsg file format.. I'd guess it would just be objects/variables/properties... So if the next game doesn't have those objects/rooms then it would fail.
I#'m not sure how _safe_ that is though, as I'm not too sure about the qsg file format.. I'd guess it would just be objects/variables/properties... So if the next game doesn't have those objects/rooms then it would fail.
Freak
27 Mar 2007, 12:34QUEST SAVED GAME FORMAT:
A qsg file is defined as follows:
- A string containing the version of the save game format
- A null
- A string with the location of the game file
- A null
- An obfuscated block containing the main data table.
An obfuscated block of type X is defined:
- (byte #n of the obfuscated block) is equal to
0xff - (byte #n of the unobfuscated block)
A table of type X is defined as follows:
- An integer holding the number of records
- A null
- That many records of type X.
The main data table is defined as follows:
- A string containing the location of the player
- A null
- A table of property data
- A table of objects
- A table of changed exits
- A table of timers
- A table of string variables
- A table of numeric variables
A record of property data is defined as follows:
- A string containing the name of the object
- A null
- the string "properties "
- (for a boolean true): the property name
- (for a boolean false): the string "not " and the property name
- (for a valued property): the property name, "=", and the property value.
- A null
(All changes to property data are to be stored, in the order in which they occurred.)
A record of object is defined as follows:
- A string containing the object name
- A null
- If the object is currently hidden, a 0x00 character, otherwise a 0x01.
- If the object is currently invisible, a 0x00 character, otherwise a 0x01.
- The object's current parent
- A null
(Only the current list of objects is to be stored)
A record of changed exits is defined as follows:
- The name of the rooms whose exit was changed
- A null
- The string "exit "
- The direction of the exit
- The string " <"
- The name of the room whose exit was changed again.
- The string ";"
- If an exit was created, " ", followed by the name of the destination room
- The string ">"
- A null
(All changes to exits are to be saved, in the order in which the occurred)
A record of timers is defined as follows:
- The name of the timer
- A null
- If the timer is currently running, a 0x00 character, otherwise 0x01.
- The current timer interval
- A null
- The time currently remaining in the interval
- A null
(Only current state of all timers is to be saved)
A record of string variables is defined as follows:
- The name of the variable
- If there is an array with that name:
- - One plus (Highest index of that array)
- - A null
- - For i from 0 to highest index
- - - The i-th string, if any
- - - A null
- If there is no such array, the string "0"
- A null
- The value of the variable
- A null
(Only current state of all variables is to be saved)
A record of numeric variables is defined as follows:
- The name of the variable
- If there is an array with that name:
- - One plus (Highest index of that array)
- - A null
- - For i from 0 to highest index
- - - The i-th string, if any
- - - A null
- If there is no such array, the string "0"
- A null
- The value of the variable
- A null
(Only current state of all variables is to be saved)
A qsg file is defined as follows:
- A string containing the version of the save game format
- A null
- A string with the location of the game file
- A null
- An obfuscated block containing the main data table.
An obfuscated block of type X is defined:
- (byte #n of the obfuscated block) is equal to
0xff - (byte #n of the unobfuscated block)
A table of type X is defined as follows:
- An integer holding the number of records
- A null
- That many records of type X.
The main data table is defined as follows:
- A string containing the location of the player
- A null
- A table of property data
- A table of objects
- A table of changed exits
- A table of timers
- A table of string variables
- A table of numeric variables
A record of property data is defined as follows:
- A string containing the name of the object
- A null
- the string "properties "
- (for a boolean true): the property name
- (for a boolean false): the string "not " and the property name
- (for a valued property): the property name, "=", and the property value.
- A null
(All changes to property data are to be stored, in the order in which they occurred.)
A record of object is defined as follows:
- A string containing the object name
- A null
- If the object is currently hidden, a 0x00 character, otherwise a 0x01.
- If the object is currently invisible, a 0x00 character, otherwise a 0x01.
- The object's current parent
- A null
(Only the current list of objects is to be stored)
A record of changed exits is defined as follows:
- The name of the rooms whose exit was changed
- A null
- The string "exit "
- The direction of the exit
- The string " <"
- The name of the room whose exit was changed again.
- The string ";"
- If an exit was created, " ", followed by the name of the destination room
- The string ">"
- A null
(All changes to exits are to be saved, in the order in which the occurred)
A record of timers is defined as follows:
- The name of the timer
- A null
- If the timer is currently running, a 0x00 character, otherwise 0x01.
- The current timer interval
- A null
- The time currently remaining in the interval
- A null
(Only current state of all timers is to be saved)
A record of string variables is defined as follows:
- The name of the variable
- If there is an array with that name:
- - One plus (Highest index of that array)
- - A null
- - For i from 0 to highest index
- - - The i-th string, if any
- - - A null
- If there is no such array, the string "0"
- A null
- The value of the variable
- A null
(Only current state of all variables is to be saved)
A record of numeric variables is defined as follows:
- The name of the variable
- If there is an array with that name:
- - One plus (Highest index of that array)
- - A null
- - For i from 0 to highest index
- - - The i-th string, if any
- - - A null
- If there is no such array, the string "0"
- A null
- The value of the variable
- A null
(Only current state of all variables is to be saved)
davidw
27 Mar 2007, 12:43bwatford wrote:We have figured out how to make the first chapter right out a code that would be required to be input by the player for the next chapter to start.
So people can't play part 2 of the game without first completing part 1 - do you think that will prove to be a popular idea?
paul_one
27 Mar 2007, 13:10.. Interesting....
Properties aren't saved as parts of the object specifications.
I'd say that suggests that properties are held in a seperate array/custom type.
I also agree with David, only allowing the players to go through the series would be a bad idea... Although, I'm thinking that the thing you're thinking about is having a 20 minute game for each 'chapter'.
Properties aren't saved as parts of the object specifications.
I'd say that suggests that properties are held in a seperate array/custom type.
I also agree with David, only allowing the players to go through the series would be a bad idea... Although, I'm thinking that the thing you're thinking about is having a 20 minute game for each 'chapter'.
Freak
27 Mar 2007, 13:21Perl code to read .qsg files:
#!/usr/bin/perl
use strict;
my $ftext;
my $ifn = $ARGV[0];
{
my $FH;
open $FH, $ifn or die "Unable to open $ifn: $!";
local $/ = undef;
$ftext = <$FH>;
}
my @chars = split //, $ftext;
my %ht = ();
my $fn;
{
my $tmp_line = '';
my @out_lines = ();
sub push_char { $tmp_line .= chr $_[0]; }
sub push_line { push @out_lines, $tmp_line; $tmp_line = ''; }
sub get_lines { return @out_lines; }
}
sub getnum {
if ($_[0] =~ /^[0-9]*$/) { return $_[0]; }
die "Bad number format '$_[0]'";
}
for ($fn = 0; $chars[$fn] ne chr 0; $fn ++) {
push_char (ord $chars[$fn]);
}
push_line();
for (++ $fn; $chars[$fn] ne chr 0; $fn ++) {
push_char (ord $chars[$fn]);
}
push_line();
for (++ $fn; $fn < @chars; $fn ++) {
my $n = 255 - ord $chars[$fn];
if ($n == 0) {
push_line();
} elsif ($n == 1) {
push_char($n); push_line();
} else {
push_char($n);
}
}
my @result = get_lines();
my $line_num = 0;
my $table_size;
sub show_result {
for (my $i = 0; $i < @result; $i ++) {
print "$i: $result[$i]\n";
}
}
if ($ARGV[1] eq '-raw') {
show_result(); die;
}
print "Save version: ", $result[$line_num ++], "\n";
print "Game File: ", $result[$line_num ++], "\n";
print "Current room: ", $result[$line_num ++], "\n";
print "\nTable A (properties?): size $result[$line_num]\n";
$table_size = getnum($result[$line_num ++]);
for (my $n = 0; $n < $table_size; $n ++) {
print " properties $n: name '", $result[$line_num++], "', data '", $result[$line_num ++], "'\n";
}
my $s0 = '';
my $s1 = chr(1);
print "\nTable B (Objects): size $result[$line_num]\n";
$table_size = getnum($result[$line_num ++]);
for (my $n = 0; $n < $table_size; $n ++) {
my ($p1, $p2, $t) = ($result[$line_num + 1], $result[$line_num + 2], '');
if ($p1 eq $s0 && $p2 eq $s0) { $t = 'HI'; }
if ($p1 eq $s0 && $p2 eq $s1) { $t = 'H '; }
if ($p1 eq $s1 && $p2 eq $s0) { $t = 'I '; }
if ($p1 eq $s1 && $p2 eq $s1) { $t = ' '; }
if ($t eq '') {
print " objt $n: name '", $result[$line_num ++], "', 1: '", $result[$line_num ++], "', 2: '", $result[$line_num ++], "', 3: '", $result[$line_num ++], "'\n";
} else {
print " $t $n: name '", $result[$line_num], "', \tparent '", $result[$line_num + 3], "'\n";
$line_num += 4;
}
}
print "\nTable C (changed exits): size $result[$line_num]\n";
$table_size = getnum($result[$line_num ++]);
for (my $n = 0; $n < $table_size; $n ++) {
print " exit $n: '", $result[$line_num ++], "' -> '", $result[$line_num ++], "'\n";
}
print "\nTable D (Timers): size $result[$line_num]\n";
$table_size = getnum($result[$line_num ++]);
for (my $n = 0; $n < $table_size; $n ++) {
my $state = '';
if ($result[$line_num+1] eq $s0) { $state = 'ON'; }
elsif ($result[$line_num+1] eq $s1) { $state = 'OFF'; }
else { $state = "state $result[$line_num+1]"; }
print " Timer $n: $result[$line_num] ($state), interval $result[$line_num+2]s, remaining time $result[$line_num+3]\n";
$line_num += 4;
}
print "\nTable E (string variables): size $result[$line_num]\n";
$table_size = getnum($result[$line_num ++]);
for (my $n = 0; $n < $table_size; $n ++) {
print " variable $n: name: '", $result[$line_num ++], "', ";
my $ts1 = getnum($result[$line_num ++]);
if ($ts1 > 0) {
my $i;
for ($i = 0; $i < $ts1; $i ++) {
print "$i -> '", $result[$line_num ++], "', ";
}
print "$i -> ";
}
print "'", $result[$line_num ++], "'\n";
}
print "\nTable F (numeric variables): size $result[$line_num]\n";
$table_size = getnum($result[$line_num ++]);
for (my $n = 0; $n < $table_size; $n ++) {
print " statvar $n: name: '", $result[$line_num ++], "', ";
my $ts1 = getnum ($result[$line_num ++]);
if ($ts1 > 0) {
print "subsize ($ts1): [";
for (my $m = 0; $m < $ts1; $m ++) {
print "'", $result[$line_num ++], "', ";
}
print "] ";
}
print "value: '", $result[$line_num ++], "'\n";
}
if ($line_num < @result) {
print "\nExcess:\n";
while ($line_num < @result) {
print "$line_num: '", $result[$line_num ++], "'\n";
}
}
Freak
27 Mar 2007, 13:22Sample deobfuscated save file:
Save version: QUEST300
Game File: F:\Program Files\Quest\hungry goblin.cas
Current room: swamp 8
Table A (properties?): size 17
properties 0: name 'worm', data 'properties hidden'
properties 1: name 'game', data 'properties found meat'
properties 2: name 'venison', data 'properties not hidden'
properties 3: name 'game', data 'properties stolen'
properties 4: name 'venison', data 'properties hidden'
properties 5: name 'game', data 'properties trollhere'
properties 6: name 'game', data 'properties trollhere'
properties 7: name 'game', data 'properties trollhere'
properties 8: name 'game', data 'properties trollhere'
properties 9: name 'game', data 'properties trollhere'
properties 10: name 'game', data 'properties trollhere'
properties 11: name 'game', data 'properties trollhere'
properties 12: name 'game', data 'properties trollhere'
properties 13: name 'game', data 'properties trollhere'
properties 14: name 'game', data 'properties trollhere'
properties 15: name 'game', data 'properties not trollhere'
properties 16: name 'biscuit', data 'properties hidden'
Table B (Objects): size 52
I 0: name 'game', parent ''
HI 1: name 'den', parent ''
HI 2: name 'tunnel', parent ''
HI 3: name 'cave entrance, by a swamp', parent ''
HI 4: name 'swamp', parent ''
HI 5: name 'in the log', parent ''
HI 6: name 'swamp 7', parent ''
HI 7: name 'swamp 16', parent ''
HI 8: name 'swamp 12', parent ''
HI 9: name 'swamp 1', parent ''
HI 10: name 'swamp 2', parent ''
HI 11: name 'swamp 3', parent ''
HI 12: name 'swamp 4', parent ''
HI 13: name 'swamp 5', parent ''
HI 14: name 'swamp 6', parent ''
HI 15: name 'swamp 8', parent ''
HI 16: name 'swamp 9', parent ''
HI 17: name 'swamp 10', parent ''
HI 18: name 'swamp 11', parent ''
HI 19: name 'swamp 13', parent ''
HI 20: name 'swamp 14', parent ''
HI 21: name 'swamp 15', parent ''
HI 22: name 'swamp 17', parent ''
HI 23: name 'swamp 18', parent ''
HI 24: name 'swamp 19', parent ''
HI 25: name 'swamp 20', parent ''
HI 26: name 'swamp 21', parent ''
HI 27: name 'swamp 22', parent ''
HI 28: name 'swamp 23', parent ''
HI 29: name 'swamp bank', parent ''
HI 30: name 'room 0', parent ''
HI 31: name 'path', parent ''
HI 32: name 'square', parent ''
HI 33: name 'outside jester', parent ''
HI 34: name 'tavern', parent ''
H 35: name 'worm', parent 'inventory'
36: name 'stick', parent 'inventory'
37: name 'dragon-slayer', parent 'cave entrance, by a swamp'
38: name 'campfire', parent 'cave entrance, by a swamp'
H 39: name 'venison', parent 'inventory'
H 40: name 'slayer2', parent 'cave entrance, by a swamp'
H 41: name 'pliers', parent 'cave entrance, by a swamp'
42: name 'log', parent 'swamp'
H 43: name 'biscuit', parent 'inventory'
44: name 'string', parent 'inventory'
45: name 'troll', parent 'swamp 18'
46: name 'pebble', parent 'swamp 14'
47: name 'dead troll', parent 'room 0'
48: name 'tooth', parent 'room 0'
49: name 'necklace', parent 'room 0'
50: name 'necklace (worn)', parent 'room 0'
51: name 'jester sign', parent 'outside jester'
Table C (changed exits): size 0
Table D (Timers): size 0
Table E (string variables): size 16
variable 0: name: 'quest.currentroom', 'swamp 8'
variable 1: name: 'quest.formatroom', 'a swamp'
variable 2: name: 'quest.objects', ''
variable 3: name: 'quest.formatobjects', ''
variable 4: name: 'quest.doorways.out', ''
variable 5: name: 'quest.doorways.out.display', ''
variable 6: name: 'quest.doorways.dirs', '|bnorth|xb, |bsouth|xb, |beast|xb, |bwest|xb, |bnortheast|xb, |bnorthwest|xb, |bsoutheast|xb or |bsouthwest|xb'
variable 7: name: 'quest.doorways.places', ''
variable 8: name: 'quest.lookdesc', 'More yellow pong. More stunted trees.' variable 9: name: 'quest.characters', ''
variable 10: name: 'quest.originalcommand', 'save'
variable 11: name: 'quest.command', 'save'
variable 12: name: 'quest.lastobject', 'stick'
variable 13: name: 'quest.error.article', 'it'
variable 14: name: 'food', 'biscuit'
variable 15: name: 'hidingplace', 'log'
Table F (numeric variables): size 2
statvar 0: name: 'tummy', value: '97'
statvar 1: name: 'trolltimer', value: '10'
paul_one
27 Mar 2007, 13:53.. It just get's stranger!
Rooms are considered objects (that are hidden and invisible by the looks of it).. And 'Game' is actually considered invisible (although I don't recall seeing that property set at all).
My first thought was to have objects and rooms held in the same array internally, but that they were still different and it would be much easier to have them seperate (although objects and containers could be in the same array - this is also a necessity as Quest can interchange between objects/containers).
I also came to the conclusion that everything needed a parent of ''. to start with (or 0 in my case).
I guess the only way Quest knows that they are different is by looking at the data it's already loaded from the ASL file..
I guess this also applies to status vars vs 'normal' vars.
.. The way properties also grow is curious.. You can see what properties were assigned to the objects/rooms, so I guess this could be a second log (although I'd hate to see how large this file gets in QuestNet!). I would have expected that the properties would be just taken from the object and saved..
I guess the double-hidden/invisible property saving is due to a 'natural evolution' of the QSG file.
Rooms are considered objects (that are hidden and invisible by the looks of it).. And 'Game' is actually considered invisible (although I don't recall seeing that property set at all).
My first thought was to have objects and rooms held in the same array internally, but that they were still different and it would be much easier to have them seperate (although objects and containers could be in the same array - this is also a necessity as Quest can interchange between objects/containers).
I also came to the conclusion that everything needed a parent of ''. to start with (or 0 in my case).
I guess the only way Quest knows that they are different is by looking at the data it's already loaded from the ASL file..
I guess this also applies to status vars vs 'normal' vars.
.. The way properties also grow is curious.. You can see what properties were assigned to the objects/rooms, so I guess this could be a second log (although I'd hate to see how large this file gets in QuestNet!). I would have expected that the properties would be just taken from the object and saved..
I guess the double-hidden/invisible property saving is due to a 'natural evolution' of the QSG file.
bwatford
27 Mar 2007, 13:53The object is that the first chapter will have about 20 rooms as a teaser, including combat, character setup etc. To get them used to the style of system that the game will use. Then following chapters get much much longer but it is very important to carry over the score because that is what triggers level advances etc. (Example: a turn check looks for there score and if score is > x then send them to a special place where they get upgrades, hitpoints etc.) and then back to there original location. But if certain things can't be carried from chapter to chapter then it won't work.
I could make the chapters playable by anyone and have a default start setting but I would think that a player would want to keep the character they created.
Like if you purchase a game on cd's and say there are 6 cd's to the game, you can't just jump in and start playing disk 6 if the other 5 havn't been played.
I think doing it this way will build user anticipation between chapters, wanting to find out what is next.
I could make the chapters playable by anyone and have a default start setting but I would think that a player would want to keep the character they created.
Like if you purchase a game on cd's and say there are 6 cd's to the game, you can't just jump in and start playing disk 6 if the other 5 havn't been played.
I think doing it this way will build user anticipation between chapters, wanting to find out what is next.
paul_one
27 Mar 2007, 14:00You can't liken one 'chapter' to one 'disc'..
As one disc is only a part of a 'game'... Which is kind of chopping up one chapter into different bits.
A closer similarity is like "half life 1" and "half life 2"... or doom 1, 2, 3 and 4... etc.
You can build a custom program which can change the save file.
Or you can basically expand on the game file you have, releasing it slowly so that the player can just unzip it over itself and play more as more is released (which I think it the best way to do it)... Although you have to make sure you don't delete rooms/objects.
As one disc is only a part of a 'game'... Which is kind of chopping up one chapter into different bits.
A closer similarity is like "half life 1" and "half life 2"... or doom 1, 2, 3 and 4... etc.
You can build a custom program which can change the save file.
Or you can basically expand on the game file you have, releasing it slowly so that the player can just unzip it over itself and play more as more is released (which I think it the best way to do it)... Although you have to make sure you don't delete rooms/objects.
007bond
28 Mar 2007, 05:12I'm pretty sure that people have tried to do this before, and have come up with some sort of solution.
I don't think using the QSG save file is the answer though.
I don't think using the QSG save file is the answer though.
bwatford
28 Mar 2007, 13:18I don't think it can be done without some external file being run. I'm looking into it.
davidw
28 Mar 2007, 23:30Why you're even bothering to do this in the first place is beyond me. One question to ask yourself before you include a new feature: is this going to make my game better?
All a system like this is going to do is annoy those people who try to play part 2 and find they can't because you've password protected it. Aside from anything, all they'd have to do to get the password is post a message on the forum asking for it and someone would tell them what it was.
I suppose I should probably mention here that you've fallen into the newbie trap of trying to write some overly ambitious uber epic of a game for your first try, instead of sticking to something a little more manageable. Unless you're the one in a million newbie who actually manages to pull it off, I can well see this going the same way as The Shadow Projekt and Business Tycoon
All a system like this is going to do is annoy those people who try to play part 2 and find they can't because you've password protected it. Aside from anything, all they'd have to do to get the password is post a message on the forum asking for it and someone would tell them what it was.
I suppose I should probably mention here that you've fallen into the newbie trap of trying to write some overly ambitious uber epic of a game for your first try, instead of sticking to something a little more manageable. Unless you're the one in a million newbie who actually manages to pull it off, I can well see this going the same way as The Shadow Projekt and Business Tycoon
Freak
28 Mar 2007, 23:48I believe that Klaustrophobia split the game into three parts, which were connected by modifying savegames, but that was because the game was too big for AGT's limits.
007bond
29 Mar 2007, 10:13I gotta agree with Davidw on this one.
I know because i fell for that trap too.
I know because i fell for that trap too.