Skip to content

Latest commit

 

History

History
240 lines (189 loc) · 9.31 KB

README.md

File metadata and controls

240 lines (189 loc) · 9.31 KB

OSRlib.NET

License Build status (main) Docs Reference Package
License: MIT Build Status Documentation API reference NuGet

OSRlib.NET logo

OSRlib.NET is a .NET Core class library written in C# that you can use as the game mechanics engine for a turn-based computer role-playing game (CRPG).

The osrlib.Core object model and API were designed with the original Bard's Tale and similar CRPGs in mind. The library is appropriate for use in any game modeled after the Dungeons & Dragons Basic/Expert (or B/X) edition or other tabletop RPG in the Old School Renaissance (OSR) style.

Add your own UI (or even CLI) that talks to the OSRlib.NET API, and you've got yourself a turn-based RPG!

⚠️ OSRlib.NET is early in development and several critical systems have yet to be built. To get an idea of what's missing, check out the open issues.

Prerequisites

  • .NET SDK 7.0+

Install the package

Run this dotnet CLI command to add a reference to your project:

dotnet add package osrlib.Core

Getting started

The OSRlib.NET object model represents well-known RPG entities like adventures, dungeons, beings (player characters and monsters), encounters, and weapons. It has an event-based interaction model for manipulating these entities, their relationships, and state.

Any game you build with OSRlib.NET will include at least these four core operations, presented here with example code and in typical order of operation:

ℹ️ TIP: You can see these code snippets in context in OSRlib.NET's test project: src/osrlib.Tests/ReadMeTests.cs

Create a character

// Get a ten-sided die ready
DiceRoll roll = new DiceRoll(new DiceHand(1, DieType.d10));

// Roll up a fighter-type character
Being fighter = new Being
{
    Name = "Blarg the Destructor",
    Defense = roll.RollDice(),
    MaxHitPoints = roll.RollDice() + 10
};
fighter.HitPoints = fighter.MaxHitPoints;
fighter.RollAbilities();

// Give Blarg a sweet sword
Weapon magicSword = new Weapon
{
    Name = "Long Sword + 1",
    Description = "A finely crafted sword, its blade dimly glows.",
    Type = WeaponType.Melee,
    DamageDie = new DiceHand(1, DieType.d8)
};
magicSword.AttackModifiers.Add(
    new Modifier { ModifierSource = magicSword, ModifierValue = 1 });
magicSword.DamageModifiers.Add(
    new Modifier { ModifierSource = magicSword, ModifierValue = 1 });
fighter.ActiveWeapon = magicSword;

// Now, add the fighter to the player's party
Party playerParty = new Party();
playerParty.AddPartyMember(fighter);

Stock the dungeon

Dungeon dungeon = new Dungeon();

// Create some monsters for an encounter
Being goblin1 = new Being
{
    Name = "Goblin Chieftain",
    Defense = 10,
    HitPoints = 10,
    MaxHitPoints = 10
};
goblin1.RollAbilities();

Being goblin2 = new Being
{
    Name = "Goblin",
    Defense = 5,
    HitPoints = 4,
    MaxHitPoints = 4
};
goblin2.RollAbilities();

// Add the goblins to the monster party
Party monsterParty = new Party();
monsterParty.AddPartyMember(goblin1);
monsterParty.AddPartyMember(goblin2);

// Add the monsters to an encounter
Encounter encounter = new Encounter
{
    EncounterParty = monsterParty,
    Position = new GamePosition(10, 10)
};

// Add the encounter to the dungeon
dungeon.Encounters.Add(encounter);

Subscribe to battle events

The Encounter, like most top-level entities in OSRlib, exposes events to notify subscribers of actions it performs and actions performed on it.

Subscribe to events like these and use them as triggers to update your game's user interface or perform other runtime actions.

// OSRlib is heavily event-driven and most top-level classes expose public events.
// Determine when and how to change the state of your game at runtime by subscribing
// to events exposed such objects. For example, to know when to prompt for target
// selection or play a sound when a monster is killed.
encounter.EncounterStarted += (sender, eventArgs) =>
    {
        Console.WriteLine($"Encounter has started! Monsters:\r\n{((Encounter)sender).EncounterParty}");
    };

// Example of subscribing to an event that you might use to update the UI state to notify the player or
// make some other change in your application at runtime.
encounter.EncounterEnded += (sender, eventArgs) =>
    {
        Encounter enc = sender as Encounter;

        if (enc.AdventuringParty.IsAlive)
        {
            Console.WriteLine("Your party has won the battle!");
        }
        else if (enc.EncounterParty.IsAlive)
        {
            Console.WriteLine("Sorry, your party has been vanquished.");
        }
    };

Start the battle

We're now ready to let the adventuring party (currently comprised of only one character, Blarg the Destructor) and the encounter party (the evil orcs) battle to the death.

// You can set encounters auto-resolve the battle as is done in this example. In a
// typical game, however, you wouldn't enable auto-battle, and instead would prompt
// your player to select a target(s) or perform some other action before proceeding
// with the next battle step.
encounter.AutoBattleEnabled = true;

// Add the adventuring party to the encounter
encounter.SetAdventuringParty(playerParty);

// Subscribe to some events on the combatants so we can respond to things
// that happen to them.
List<Being> combatants = encounter.AdventuringParty.Members.Concat(encounter.EncounterParty.Members).ToList();
foreach (Being combatant in combatants)
{
    combatant.SelectedAsTarget += (s, e) =>
        {
            Being attackedBeing = s as Being;
            BeingTargetingEventArgs args = e as BeingTargetingEventArgs;

            Console.WriteLine($"{e.TargetingBeing} attacks {attackedBeing} with their {e.TargetingBeing.ActiveWeapon}...");
        };
    combatant.ActionPerformed += (s, e) =>
        {
            GameActionEventArgs actionArgs = e as GameActionEventArgs;
            GameAction action = actionArgs.Action;

            if (action.Victor.Equals(combatant))
            {
                Console.WriteLine($"{combatant} rolled a {action.AttackRoll} and hit for {action.DamageRoll} points of damage.");
            }
            else
            {
                Console.WriteLine($"{combatant} rolled a {action.AttackRoll} and missed.");
            }
        };
    combatant.Killed += (s, e) =>
        {
            Console.WriteLine($"{((Being)s).Name} was killed!");
        };
}

// Start the battle. This will fire the EncounterStarted event we subscribed to
// above, and since we set this encounter to resolve all combat automatically with
// AutoBattleEnabled, each member of both parties takes turns attacking each other
// until one side is defeated.
encounter.StartEncounter();

Display event data

The battle resolves fully (because we set Encounter.AutoBattleEnabled = true) and, because we subscribed Being and Encounter events, we can see what transpires during the battle:

Encounter has started! Monsters:
[0] Goblin Chieftain    Hit points: 10
[1] Goblin      Hit points: 4

Blarg the Destructor (18/18) attacks Goblin (4/4) with their Long Sword + 1...
Goblin was killed!
Blarg the Destructor (18/18) rolled a 9 (1d20+1) and hit for 6 (1d8+1) points of damage.
Goblin Chieftain (10/10) attacks Blarg the Destructor (18/18) with their Fists...
Goblin Chieftain (10/10) rolled a 16 (1d20-1) and hit for 0 (1d2-1) points of damage.
Blarg the Destructor (18/18) attacks Goblin Chieftain (10/10) with their Long Sword + 1...
Blarg the Destructor (18/18) rolled a 15 (1d20+1) and hit for 7 (1d8+1) points of damage.
Goblin Chieftain (3/10) attacks Blarg the Destructor (18/18) with their Fists...
Goblin Chieftain (3/10) rolled a 15 (1d20-1) and hit for 1 (1d2-1) points of damage.
Blarg the Destructor (17/18) attacks Goblin Chieftain (3/10) with their Long Sword + 1...
Your party has won the battle!
Goblin Chieftain was killed!
Blarg the Destructor (17/18) rolled a 13 (1d20+1) and hit for 3 (1d8+1) points of damage.

Next steps

This README was a quick intro to a few of the types and operations available in OSRlib. Here are some other resources to help you use OSRlib.NET in your turn-based RPG:

Have fun!