Monster Slaying Design in Monsters (for Adventurers)

Hi folks! First of all, I just want to say I love this metaverse. I think the Loot project has opened up a Pandora’s box that will create a massive explosion of community-owned works.

One of my favourite experiences in gaming was encountering Forgotten Beasts in Dwarf Fortress for the first time. Forgotten Beasts in DF are procedurally generated monstrosities that will wreck you and your dwarves if left unchecked.

500px-DF2010ForgottenBeast1

I was keen to bring this monster slaying experience into the Loot metaverse (to give you something to do with your Loot) and developed https://monstersforadventurers.com/.

Monsters (for Adventurers) is a ‘lego’ piece that lets you pit your Loot against unique monsters.

Overview

In this topic, I’d like to share how the Monster Slaying mechanic in Monsters(for Adventurers) works and how it builds on top of the Loot and Dom’s LootComponents contract behind the scenes. My hope is it will help and inspire builders, as well as offer the Loot community an interesting new building block to use in their future projects.

The Monsters

First, let’s look at what a Monster is. Each Monster is a bag of randomly generated traits and weaknesses. Monster traits are fully accessible on-chain through the get<Trait>(monsterId) and traitsOf(monsterId) getter functions.

For example, Monster #1 is ‘Weak to Long Swords’. Each Monster is weak to one of 18 weapon types in Loot.

From the original Loot contract, there are a total of 18 Weapon Types:

  string[] private weapons = [
    "Warhammer", // 0
    "Quarterstaff", // 1
    "Maul", // 2
    "Mace", // 3
    "Club", // 4
    "Katana", // 5
    "Falchion", // 6
    "Scimitar", // 7
    "Long Sword", // 8
    "Short Sword", // 9
    "Ghost Wand", // 10
    "Grave Wand", // 11
    "Bone Wand", // 12
    "Wand", // 13
    "Grimoire", // 14
    "Chronicle", // 15
    "Tome", // 16
    "Book" // 17
  ];

The Monsters contract uses both the Loot and the LootComponents utility contract to extract and use this information:

interface ILoot {
  function ownerOf(uint256 tokenId) external view returns (address);
  function getWeapon(uint256 tokenId) external view returns (string calldata);
}

interface ILootComponents {
  function weaponComponents(uint256 tokenId) external view returns (uint256[5] memory);
  // uint256[5] =>
  //     [0] = Item ID
  //     [1] = Suffix ID (0 for none)
  //     [2] = Name Prefix ID (0 for none)
  //     [3] = Name Suffix ID (0 for none)
  //     [4] = Augmentation (0 = false, 1 = true)
}


ILoot public constant loot = ILoot(0xFF9C1b15B16263C61d017ee9F65C50e4AE0113D7);
ILootComponents public constant lootComponents = ILootComponents(0x3eb43b1545a360d1D065CB7539339363dFD445F3);

Note that the LootComponents contract is a utility for getting the weapon index which we use to determine Monster weaknesses.

Slaying a Monster

Adventurers can attempt to slay a Monster by calling the contracts slay(uint monsterId, uint lootId) function. First, we randomly generate a monster’s weakness based on their tokenId:

  function getWeakness(uint256 tokenId) public view returns (string memory) {
    return weapons[getWeaknessType(tokenId)];
  }

  function getWeaknessType(uint256 tokenId) internal view returns (uint256) {
    uint256 rand = random(string(abi.encodePacked("weakness", toString(tokenId))));
    return rand % weapons.length;
  }

Then, using the LootComponents contract we find their weapon index based on the lootId passed:

lootComponents.weaponComponents(lootId)[0];

The two values are compared to see if the Loot’s weapon is strong against a Monster:

  function canSlay(uint256 tokenId, uint256 lootId) public view returns (bool) {
    return getWeaknessType(tokenId) == lootComponents.weaponComponents(lootId)[0];
  }

For projects using Monsters as a building block, this can mean the user does a critical hit or get a bonus when fighting this Monster.

Finally, there is a slay() function reserved for Monster owners:

  function slay(
    uint256 tokenId,
    uint256 lootId,
    string calldata name
  ) public {
    require(msg.sender == loot.ownerOf(lootId), "Not loot owner");
    require(canSlay(tokenId, lootId), "Immune");
    setName(tokenId, name);
    slayerOf[tokenId] = msg.sender;
    slainWith[tokenId] = lootId;
    emit Slain(msg.sender, tokenId, lootId, name);
  }

Note that slaying a monster adds a slaying record for the Monster:

Projects building on Monsters will likely implement their own slay() functions.

Monster Slaying Summary

In summary, Monsters adds a set of randomized creatures who are weak to certain weapon types. Stats and other functionality are omitted for future applications to create to better fit the mechanics the community wants to build.

Bonus Proposal: Procedural Generation of Monster Stats

Here’s an idea of how you can use Monsters in an ‘encounter’ or ‘battle’ protocol.

In each encounter, the stats of each Monster can be generated based on a mix of information:

  • Monster traits (name, color, race, quirks, etc.) - for example, named Monsters might be more challenging
  • Encounter level (scaled to Adventurer / Loot?)
  • Random number from Chainlink VRF
  • Other on-chain / off-chain data

Outcomes are determined based on how the adventurers’ stats compare against the monsters.
The same stats can be used to determine what Loot you get as rewards.

Conclusion

Thank you for reading.
Please feel free to leave any of your thoughts and feedback here!

5 Likes

Great idea and I really like how this connects to the original weapon types. I think building relationships between different building blocks is where things will get really interesting. Also happy to see some Dwarf Fortress and Qud images in there :slight_smile:

Yes, I agree completely! Just as an example, we can imagine a ‘Quests’ or ‘Encounters’ protocol using the Monsters ‘legos’, Dungeons, and others as building blocks. The potential for remixability between protocols is endless!

1 Like

DF is an incredibly complex game that’s been in development for decades. I’d love to see something like this come into fruition, and quickly. How can I help?

Hey! Building something as complex as DF would be very gas inefficient if every move would happen on-chain, but something like https://zkga.me/ with zkSnarks would be feasible I think.

Feel free to join the Monsters Discord: Monsters (for Adventurers) Guild