1
\$\begingroup\$

So I have made a small RPG where you can create char, update skills, buy items, fight other users etc. Now to make the game more extended I need quests. And I need a little bit of help here.

Right now I have these tables:

Quests: id title etc

ActiveQuests: id quest_id user_id (this table fills when user starts a quest)

QuestBosses id quest_id (Quests bosses)

Now I need somehow to develop goals like I should have a quest_goals table and there store goals by quest_id I believe ? But then again every goal shuold be coded seperately and if I would want to make new quest through let's say admin panel this approach won't work. Maybe someone have any experience? I'm using NodeJS and Mongoose for this project

\$\endgroup\$
  • 5
    \$\begingroup\$ I think in order to answer this question we would need to know more about what a "quest" and a "quest goal" actually means in the context of your game. It might seem obvious to you because you worked on your game for a while, but it is not obvious at all to your readers. \$\endgroup\$ – Philipp Nov 4 '18 at 16:28
1
\$\begingroup\$

I far too often used to start making tables before taking the time to really design. I'd recommend taking some time to make a wide variety of examples of quests. Quests that link to other quests, quests that have more than one goal, multiple goals, goals in sequence etc. Then parameterize them in a way that would allow you to programmatically define them. Once that feels solid, return to actual schema design.

\$\endgroup\$
  • \$\begingroup\$ This is more of a comment than an answer. However posed question is too broad to be able to answer it anyway. \$\endgroup\$ – Kromster Nov 7 '18 at 5:39
0
\$\begingroup\$

But then again every goal shuold be coded seperately

I guess this means the engine needs to understand the difference between, say, Slay Boss X and Fetch 10 Carrots.

Sticking to these examples, the game world needs the boss character and 10 carrot objects. We don't know how you create them and add them to the world, if they respawn, if they are part of static map data, if they are part of a central object database -- but all that doesn't matter much if you worry about how to connect a quest to either the boss or all these carrot objects, because:

One possible abstraction I want to suggest are events that change flags.

  • "When this enemy is killed, post event to set flag with UUID 123456789 to 1."
  • "When this carrot is picked up, post event to increase flag with UUID 45678 by +1."
  • "When this carrot is dropped, post event to decrease flag with UUID 45678 by -1."

When

Then your quests can be encoded as:

  • "Kill Boss X" == flag[123456789l] > 0
  • "Fetch 10 Carrots == flag[45678] = 10

The engine has to support events like character.onKilled, item.onPickedUp, item.onDropped, etc., but this scales rather well. All you need is a database of flags for each user (which are just hash tables or a variable array or siliar), and a database of quests that check a condition.

In this example, flags always are numbers. The default is 0 (e.g. when the flag is not stored in the database yet) and events can change the values. That's simple, but it works for a lot of things and doesn't take much space.

\$\endgroup\$
-1
\$\begingroup\$

Although I had to make assumptions on how your quest system would function, it feels this is doathat the fact that MongoDB supports a dynamic schema.

How about the following sample schema?

User: {
    id: ObjectId,
    username: string,
    coins: integer
}

Character: {
    id: ObjectId.
    name: string,
    exp: integer,
    level: integer,
    hp_capacity: integer,
    hp_current: integer,
    attack: integer,
    defence: integer,
    spawned: boolean
}

UserOwnedCharacter: {
    ...Character,
    user_id: ObjectId
}

// Can be embedded as an array in the user collection.
UserOwnedQuest: {
    id: ObjectId,
    user_id: ObjectId,

    // either
    discovered: boolean,
    in_progress: boolean,
    completed: boolean

    // or
    state: string
}

Quest: {
    id: ObjectId,

    quest_boss_id: ObjectId,

    // either
    on_begin_gameplay_prerequisite_id: ObjectId,
    on_begin_gameplay_effect_id: ObjectId,
    on_complete_gameplay_prerequisite_id: ObjectId,
    on_complete_gameplay_effect_id: ObjectId,

    // or
    on_begin_gameplay_prerequisite: {data},
    on_begin_gameplay_effect: {data},
    on_complete_gameplay_prerequisite: {data},
    on_complete_gameplay_effect: {data},

    title: string
}

Gameplay Prerequisite: {
    id: ObjectId,
    "mechanicType": [mechanicPrereq1, mechanicPrereq2, ..., mechanicPrereqN],
    ...
}

Mechanic Prerequisite: {
    id: ObjectId,

    equal: {
        attribute: any,
        ...
    },

    greaterThan: {
        attribute: numeric,
        ...
    }
    greaterThanOrEqual: {
        attribute: numeric,
        ...
    },

    lessThan: {
        attribute: numeric,
        ...
    },
    lessThanOrEqual: {
        attribute: numeric,
        ...
    }
}

Gameplay Effect: {
    id: ObjectId,
    "mechanicType": [mechanicEffect1, mechanicEffect2, ..., mechanicEffectN]
}

Mechanic Effect: {
    id: ObjectId,

    set: {
        attribute: any,
        ...
    },
    add: {
        attribute: numeric,
        ...
    }
}

Example:

User 1: {
    id: "User1",
    coins: 100,
    ...
}

User Early Level Prerequisite: {
    id: "UserAtEarlyLevel",
    greaterThan: { "level": 2 },
    lessThan: { "level": 5 }
}

User Has Coins 100: {
    id: "UserHasCoins",
    greaterThanOrEqual: { "coins": 100 }
}

User Add Coins 500 Effect: {
    id: "UserAddCoins500",
    add: { "coins": 500 }
}

User Remove Coins 100 Effect: {
    id: "UserRemoveCoins100",
    add: { "coins": -100 }
}

Boss 1: {
    id: "Boss1",
    ...
}

User1 Good Guy 1: {
    id: "GoodGuy1",
    ...
}

User1 Good Guy 2: {
    id: "GoodGuy1",
    ...
}

Character Spawned Prerequisite: {
    id: "CharSpawned",
    equal: { "spawned": true }
}

Character Kill Prerequisite: {
    id: "CharKill",
    equal: { "hp_current": 0 }
}

Character EXP Effect: {
    id: "CharGainExperience100",
    add: { "exp": 100 }
}

Character Damage Effect: {
    id: "CharDamage50",
    add: { "hp_current": -50 }
}

Character Kill Effect: {
    id: "CharKill",
    set: { "hp_current": 0 }
}

Gameplay Prerequisite: {
    id: "Boss1Spawned",
    character: {
        "Boss1": [Character Spawned Prerequisite]
    },
    user: {
        "this": [User Early Level Prerequisite, User Has Coins 100]
    }
}

Gameplay User Consume Coins Effect: {
    id: "Boss1Killed",
    user: {
        "this": [User Remove Coins 100]
    }
}

Gameplay Boss1 Killed Prerequisite: {
    id: "Boss1Killed",
    character: {
        "Boss1": [Character Kill Prerequisite]
    },
    user: {
        "this": [User Early Level Prerequisite, User Add Coins 500]
    }
}

Quest {
    id: "FirstQuest",

    quest_boss_id: "Boss1",

    on_begin_gameplay_prerequisite: Gameplay Prerequisite 1,
    on_begin_gameplay_effect: Gameplay User Consume Coins Effect,

    on_complete_gameplay_prerequisite: Gameplay Boss1 Killed Prerequisite,
    on_complete_gameplay_effect: User Add Coins 500 Effect,

    title: "Kill the boss to get the bounty"
}

It can be expanded to get as complex as you want to.

Now for this to work, you will need to:
- query the DB for the data
- Glue them together in memory and run the prerequisites and effects
- update the in-memory objects in the DB when necessary

You will have to take care of caching those data into memory and saving when necessary.

Good luck!
Wow, that was long...

\$\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.