0
\$\begingroup\$

I have been developing a text-based rpg in C++, using sqlite as my database.

When it comes to interactions that change the world around the player in some permanent fashion I have been unable to find a satisfactory method of storing them.

Example

The player kills a character then if they return to the same area I do a check in my "game_data" table for "character_x_is_dead" and if the character is dead then the player sees the body on the floor, if they are alive then the player can talk to them.

Is there a better way to handle this type of thing without having to impose conditions on an area that are checked and then the correct output is picked from a list of possibilities? (I'm just thinking about how as the game gets bigger and the player does more things this table is just going to grow more and more with potentially a lot of the contents becoming no longer needed)

Edit - clarification about "list of interactions"

By this phrase I mean any interactions that may alter how the player is allowed to interact with an area, hopefully these examples might make it clearer:

  1. The first time the player talks with the innkeeper at the starting town they get given some money - when they talk to the innkeeper the next time, the money adding event should not fire again.
  2. After the player has gone through a dungeon, any chests they open should still be empty if they come back later.
  3. If the player helps a character then the next time they talk to the character they give one of their positive responses instead of their standard ones.
\$\endgroup\$
3
\$\begingroup\$

Probably the cleanest way of doing this is just to explicitly store your entities as classes:

class Entity
{
   Room location;
   bool isDead;
   ...
   etc.
}

And just store a flat list of all your entities in the world.

List<Entity> entities;

You can also store, for each room, a list of entities that are currently in the room:

class Room
{
   List<Entity> entities;
}

Then, your check simply becomes:

foreach(Entity entity in room.entities)
{ 
   if(entity.isDead)
   {
        // Do something
   }
}

In order to prevent entities from building up, you may want to delete them under certain conditions.

\$\endgroup\$
  • \$\begingroup\$ Thanks, I was thinking this (and started to implement it) for single play-throughs, but what about when loading a save-game? \$\endgroup\$ – Kvothe Mar 18 '15 at 23:36
  • \$\begingroup\$ It doesn't work any differently for a save game. Just store the parameters for each entity in your save file. It will help to give every entity an ID \$\endgroup\$ – mklingen Mar 18 '15 at 23:40
  • \$\begingroup\$ Sorry, I was meaning, would saving the list of interactions that may need to be checked against in a table in the database be best or is would you recommend another way to store it? \$\endgroup\$ – Kvothe Mar 18 '15 at 23:46
  • \$\begingroup\$ I'm not sure what you mean by "list of interactions". Maybe you can modify your question? \$\endgroup\$ – mklingen Mar 18 '15 at 23:47
1
\$\begingroup\$

I assume that you're asking because file sizes would be too large to make copies of your SQLite file for each "new game", so I don't believe you have any alternative. Otherwise I'd say just copy that original game state and alter it directly.

You may consider the idea of firing a sort of OnLoad() event whenever a new area is visited. It could check the time of death on each enemy found in that area and "expire" them away from your data file before you finally display that area to the user.

It might be a bit tricky, but you could even have another thread combing for expirable changes in your gamestate whenever your main thread is not actively trying to access the file. You'd want to think of a clever of indexing "expirable" state changes however.

\$\endgroup\$
  • \$\begingroup\$ I hadn't thought about simply copying the original game state and altering a copy of it. I'll have a look at structuring it to make that easy to do, and also into your other suggestions, thanks. \$\endgroup\$ – Kvothe Mar 19 '15 at 0:05
1
\$\begingroup\$

You have two initial options: to hard-code the conditions or to use scripts.

Hard-coding is quick and great for prototypes, but it allows 0 flexibility. I expect your game to out-grow this very quickly.

Using a script takes more time to get started because you need a script file parser first. Apart from proven scripting languages like LUA, you can devise your own simple instructions.

With scripting in place, I can think of two ways to store state of the game world:

  1. introduce simple flags
  2. put all interaction events into an event store and check for occurences of kinds of events in the script; the series of events is a log of what the player has done (with which you could play back all interactions in the game; not the movements, of course)

The event store approach will work well with a SQLite database, if one .sqlite file equals a save game.

Flags are simpler, though. Savegames need to be a list of flags only.

Using state Flags

Flags work like this: you create a flags array of N integers. This array contains all game states.

Your script may trigger setting flags[107] = 3 through something like SETFLAG 107, 3 in a dialogue to signify that some NPC has revealed a detail. Conditional dialogues are possible checking the flags for a value in the scripts: IF 107 > 2 ... ENDIF

Chests on your map can be populated using map launch scripts: IF 222 = 0 fill with items and close; ELSE make it empty.

Flags are very low-tech and easily work for most kinds of RPGs. They're not convenient to manage, because the numbers won't tell what they're about. You have to keep track of them in spreadsheets.

Example

I got most of this flags-related stuff from Jeff Hangartner aka Tsugumo and FrozenEmu from old QuickBasic times.

The simple scripts I devised 15 years ago with their help look like this:

checkflag,5,ST,1,a
kollide,21,39,1
end
tag,a
kollide,21,39,0
end

Which in pseudo-code means:

if flag[5] < 1 
  goto A
else 
  add_collision_block(21, 39)
  exit
endif

A:
remove_collision_block(21, 39)
exit
\$\endgroup\$
0
\$\begingroup\$

tl;dr

You have rooms, you need "keep an eye on them" by saving them and their contents (NPCs / items) to your sqlite. Each room has entities that can be in different states, you need possibly a simple Enum to save those states, the one row per entity can "remember" that entities state for you.

Elaborate answer:

You are mixing two things together. One is the state of the world, places, NPC's and items. The other is the player's activity. Normally, you wouldn't be storing player activity, and instead storing the state of the game world and all entities (places / NPC's / items) inhabiting it.

The reason being, that some changes occur due to other things. For instance, time passes by and the forest becomes dark as the night draws near (unrelated to player actions).

Each room / place needs to be aware of its state and its inhabitants. It should also have some sort of template (with blanks) that gets filled by function calls to the NPCs and items in that room.

For instance:

Some Room:

Template text:

You are in a <insert room.description(room.current_state)>. There is a <insert entity.description(entity.current_state)>, in the <insert entity.description(positional_state)>.

Obviously the template needs to be more elaborate than this (possibly). The idea is that you need to maintain a Table or during play a Vector of Rooms where each Room knows:

  1. Which room indices it is connected to.
  2. How to describe itself in it's state.
  3. What entities are inside.
  4. It's own description's template needs "blanks" inthat get filled with useful information about the entities inside it.

If there are very few states an entity can be in (dead or alive, there or gone), then it is likely that you can define a simple Enum to contain the state of the entity. Then you create a switch that returns the correct string description for each state. If the states are more complex then you may need to use a more sophisticated funtion.

\$\endgroup\$
0
\$\begingroup\$

How about using Set Theory to model your data as relations (this is easy with any relational database, or any language/library support for sets and tuples):

Imagine a relation killed_by(a, b) where a and b are both members of the set of all living things. character_x_is_dead when there exists any solution to killed_by(?, b). You could store this on disk or in memory as a set of tuples (a, b). A set is simply a list with no duplicates; there are lots of ways to search sets quickly.

A chest has a relation contains(a, b) where a is a member of the set of all chests (or all things that can contain anything -- the player's inventory can contain things too!) and b is a member of the set of all items (or the ID that represents it). You similarly store this on disk or in memory as a list of tuples.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.