diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc
new file mode 100644
index 00000000..2b6ccc33
--- /dev/null
+++ b/cadence/contracts/FGameMishal.cdc
@@ -0,0 +1,3934 @@
+/**
+> Author: Fixes Lab
+
+# FGameMishal
+
+This contract is used to define the basic game elements.
+The basic elements of a Mishal Game will be defined here.
+
+*/
+import "Burner"
+import "FungibleToken"
+// Fixes Imports
+import "Fixes"
+import "FixesHeartbeat"
+
+access(all) contract FGameMishal {
+ // Entitlements for the Editor role
+ access(all) entitlement Editor;
+ // Entitlements for the Host role (Like the host of the game)
+ access(all) entitlement Host;
+ // Entitlements for the Player role (Like the player of the game)
+ access(all) entitlement Player;
+
+ // ----- Events -----
+
+ access(all) event LibrarySettingChanged(_ library: Address, key: UInt8, value: Int64)
+ access(all) event LibraryEntryAdded(_ library: Address, _ category: UInt8, _ uuid: UInt64, _ name: String, _ tags: [String])
+ access(all) event LibraryEntryRemoved(_ library: Address, _ category: UInt8, _ uuid: UInt64)
+
+ access(all) event UnitStatusApplied(_ unitUID: UInt64, _ attributes: Attributes, _ defence: Defence, _ potentiality: Potentiality)
+
+ access(all) event EntryDeposited(_ library: Address, _ category: UInt8, _ uuid: UInt64, _ amount: UFix64, _ to: Address?, entryUUID: UInt64, collectionUUID: UInt64)
+ access(all) event EntryWithdrawn(_ library: Address, _ category: UInt8, _ uuid: UInt64, _ amount: UFix64, _ from: Address?, entryUUID: UInt64, collectionUUID: UInt64)
+
+ access(all) event CreatureItemEquipped(_ library: Address, _ item: String, _ owner: Address?, itemUUID: UInt64, uuid: UInt64)
+ access(all) event CreatureItemUnequipped(_ library: Address, _ item: String, _ owner: Address?, itemUUID: UInt64, uuid: UInt64)
+
+ access(all) event CreatureSettingUpdated(_ type: String, _ uuid: UInt64, _ setting: UInt8, _ value: Int64)
+ access(all) event CreatureBioPromptAdded(_ owner: Address?, uuid: UInt64, _ prompt: String)
+
+ access(all) event PawnAddedToPocket(_ library: Address, _ owner: Address?, _ template: String?, _ uuid: UInt64)
+ access(all) event PawnAttachedToBoard(_ library: Address, _ owner: Address, _ pawnUuid: UInt64, _ board: Address, _ boardUuid: UInt64)
+ access(all) event PawnDetachedFromBoard(_ library: Address, _ owner: Address, _ pawnUuid: UInt64, _ board: Address, _ boardUuid: UInt64)
+
+ access(all) event PawnPotentialityGained(_ owner: Address?, _ amount: UInt64, uuid: UInt64)
+ access(all) event PawnPotentialityConsumed(_ owner: Address?, _ consume: UInt64, _ usable: UInt64, _ used: UInt64, uuid: UInt64)
+ access(all) event PawnAttributeUpgraded(_ owner: Address?, _ type: UInt8, _ amount: UInt64, uuid: UInt64)
+ access(all) event PawnAbilityCultivated(_ owner: Address?, _ ability: String, _ consume: UInt64, _ abilityUp: UInt64, uuid: UInt64)
+
+ access(all) event PawnHealthReset(_ owner: Address?, _ strength: Int64, _ vitality: Int64, _ spirit: Int64, uuid: UInt64)
+ access(all) event PawnHealthRecovered(_ owner: Address?, _ type: UInt8, _ amount: Int64, uuid: UInt64)
+ access(all) event PawnHealthDamaged(_ owner: Address?, _ type: UInt8, _ amount: Int64, uuid: UInt64)
+
+ access(all) event PawnPositionUpdated(_ board: Address, _ boardUuid: UInt64, unit: UInt64, x: Int32, y: Int32)
+
+ access(all) event HostBoardCreated(_ container: Address, _ uuid: UInt64)
+ access(all) event HostBoardOpened(_ host: Address, _ container: Address, _ id: UInt64)
+ access(all) event HostBoardClosed(_ host: Address, _ container: Address, _ id: UInt64)
+
+ access(all) event HostOperatorSpawnerUpdated(_ host: Address, _ uuid: UInt64, _ pawnSpawner: Address, _ pawnSpawnerUuid: UInt64)
+ access(all) event HostOperatorBoardContainerAdded(_ host: Address, _ uuid: UInt64, _ container: Address)
+ access(all) event HostOperatorBoardContainerRemoved(_ host: Address, _ uuid: UInt64, _ container: Address)
+
+ // ----- Contract Level Variables -----
+
+ // The counter variable for the library items
+ access(all) let libraryItems: {Address: UInt64}
+
+ access(all) let libraryStoragePath: StoragePath
+ access(all) let libraryPublicPath: PublicPath
+
+ access(all) let pocketStoragePath: StoragePath
+ access(all) let pocketPublicPath: PublicPath
+
+ access(all) let hostBoardContainerPublicPath: PublicPath
+
+ access(all) let hostOperatorStoragePath: StoragePath
+ access(all) let hostOperatorPublicPath: PublicPath
+
+ // ----- Resources -----
+
+ access(all) enum LibrarySettings: UInt8 {
+ access(all) case DEFAULT_POTENTIALITY
+ access(all) case DEFAULT_ATTRIBUTE_VALUE
+ }
+
+ access(all) enum AttributeType: UInt8 {
+ access(all) case STRENGTH
+ access(all) case VITALITY
+ access(all) case SPIRIT
+ }
+
+ access(all) enum AttackType: UInt8 {
+ access(all) case PHYSICAL
+ access(all) case EROSION
+ access(all) case OCCULT
+ access(all) case TRUE_DAMAGE
+ }
+
+ access(all) enum DefenceType: UInt8 {
+ access(all) case PHYSICAL // To defend physical damage
+ access(all) case ENDURANCE // To defend erosion damage
+ access(all) case RESISTANCE // To defend occult damage
+ }
+
+ access(all) enum LibraryCategory: UInt8 {
+ access(all) case OBJECT
+ access(all) case ITEM
+ access(all) case ABILITY
+ access(all) case SHAPE
+ access(all) case FEATURE
+ access(all) case CREATURE
+ }
+
+ access(all) enum EquipSlot: UInt8 {
+ access(all) case WEAPONS // 武器
+ access(all) case HEAD // 头饰
+ access(all) case ARMOR // 盔甲
+ access(all) case FEET // 鞋子
+ access(all) case HARNESS // 马具
+ access(all) case EARRINGS // 耳饰
+ access(all) case NECK // 项链
+ access(all) case RINGS // 戒指
+ access(all) case WRISTS // 手镯
+ access(all) case GLOVES // 手套
+ access(all) case FOOTWEAR // 足饰
+ access(all) case BELT // 腰饰
+ access(all) case TAIL // 尾饰
+ access(all) case SHAWL // 披肩
+ access(all) case COAT // 服装
+ access(all) case MAKEUP // 妆饰
+ access(all) case ACCESSORY // 配饰
+ }
+
+ access(all) enum CreatureSettings: UInt8 {
+ access(all) case GENDER
+ access(all) case FORM
+ access(all) case SIZE
+ access(all) case MOVE_SPEED
+ access(all) case PERCEPTION_RANGE
+ access(all) case OCCUPY_RANGE
+ }
+
+ // The Public Library resource is used to store the settings of Mishal Game.
+ access(all) resource Library {
+ access(contract) let settings: {LibrarySettings: Int64}
+
+ access(self) let objects: @{UInt64: Object}
+ access(self) let items: @{UInt64: Item}
+ access(self) let abilities: @{UInt64: Ability}
+ access(self) let shapes: @{UInt64: Shape}
+ access(self) let features: @{UInt64: Feature}
+ access(self) let creatures: @{UInt64: Creature}
+ access(self) let nameToUID: {LibraryCategory: {String: UInt64}}
+ access(self) let tagToUIDs: {LibraryCategory: {String: [UInt64]}}
+
+ init() {
+ self.settings = {
+ LibrarySettings.DEFAULT_POTENTIALITY: 36,
+ LibrarySettings.DEFAULT_ATTRIBUTE_VALUE: 3
+ }
+ self.objects <- {}
+ self.items <- {}
+ self.abilities <- {}
+ self.shapes <- {}
+ self.features <- {}
+ self.creatures <- {}
+ self.nameToUID = {}
+ self.tagToUIDs = {}
+
+ // initialize the nameToUID and tagToUIDs
+ self.nameToUID[LibraryCategory.OBJECT] = {}
+ self.nameToUID[LibraryCategory.ITEM] = {}
+ self.nameToUID[LibraryCategory.ABILITY] = {}
+ self.nameToUID[LibraryCategory.SHAPE] = {}
+ self.nameToUID[LibraryCategory.FEATURE] = {}
+ self.nameToUID[LibraryCategory.CREATURE] = {}
+
+ self.tagToUIDs[LibraryCategory.OBJECT] = {}
+ self.tagToUIDs[LibraryCategory.ITEM] = {}
+ self.tagToUIDs[LibraryCategory.ABILITY] = {}
+ self.tagToUIDs[LibraryCategory.SHAPE] = {}
+ self.tagToUIDs[LibraryCategory.FEATURE] = {}
+ self.tagToUIDs[LibraryCategory.CREATURE] = {}
+ }
+
+ access(Editor)
+ fun setSetting(key: LibrarySettings, value: Int64) {
+ self.settings[key] = value
+
+ emit LibrarySettingChanged(self.owner?.address ?? panic("Owner not found"), key: key.rawValue, value: value)
+ }
+
+ access(Editor)
+ fun addObject(object: @Object) {
+ pre {
+ self.borrowNameToUIDDictionary(LibraryCategory.OBJECT)[object.name] == nil:
+ "Object name already exists"
+ }
+ let uuid = object.uuid
+ let name = object.name
+ let tags = object.tags
+ self.setNameAndTags(LibraryCategory.OBJECT, uuid, name, tags)
+
+ self.objects[uuid] <-! object
+
+ emit LibraryEntryAdded(
+ self.owner?.address ?? panic("Owner not found"),
+ LibraryCategory.OBJECT.rawValue,
+ uuid,
+ name,
+ tags
+ )
+ }
+
+ access(Editor)
+ fun addItem(item: @Item) {
+ pre {
+ self.borrowNameToUIDDictionary(LibraryCategory.ITEM)[item.name] == nil:
+ "Item name already exists"
+ }
+ let uuid = item.uuid
+ let name = item.name
+ let tags = item.tags
+ self.setNameAndTags(LibraryCategory.ITEM, uuid, name, tags)
+
+ self.items[uuid] <-! item
+
+ emit LibraryEntryAdded(
+ self.owner?.address ?? panic("Owner not found"),
+ LibraryCategory.ITEM.rawValue,
+ uuid,
+ name,
+ tags
+ )
+ }
+
+ access(Editor)
+ fun addAbility(ability: @Ability) {
+ pre {
+ self.borrowNameToUIDDictionary(LibraryCategory.ABILITY)[ability.name] == nil:
+ "Ability name already exists"
+ }
+ let uuid = ability.uuid
+ let name = ability.name
+ let tags = ability.tags
+ self.setNameAndTags(LibraryCategory.ABILITY, uuid, name, tags)
+
+ self.abilities[uuid] <-! ability
+
+ emit LibraryEntryAdded(
+ self.owner?.address ?? panic("Owner not found"),
+ LibraryCategory.ABILITY.rawValue,
+ uuid,
+ name,
+ tags
+ )
+ }
+
+ access(Editor)
+ fun addShape(shape: @Shape) {
+ pre {
+ self.borrowNameToUIDDictionary(LibraryCategory.SHAPE)[shape.name] == nil:
+ "Shape name already exists"
+ }
+ let uuid = shape.uuid
+ let name = shape.name
+ let tags = shape.tags
+ self.setNameAndTags(LibraryCategory.SHAPE, uuid, name, tags)
+
+ self.shapes[uuid] <-! shape
+
+ emit LibraryEntryAdded(
+ self.owner?.address ?? panic("Owner not found"),
+ LibraryCategory.SHAPE.rawValue,
+ uuid,
+ name,
+ tags
+ )
+ }
+
+ access(Editor)
+ fun addFeature(feature: @Feature) {
+ pre {
+ self.borrowNameToUIDDictionary(LibraryCategory.FEATURE)[feature.name] == nil:
+ "Feature name already exists"
+ }
+ let uuid = feature.uuid
+ let name = feature.name
+ let tags = feature.tags
+ self.setNameAndTags(LibraryCategory.FEATURE, uuid, name, tags)
+
+ self.features[uuid] <-! feature
+
+ emit LibraryEntryAdded(
+ self.owner?.address ?? panic("Owner not found"),
+ LibraryCategory.FEATURE.rawValue,
+ uuid,
+ name,
+ tags
+ )
+ }
+
+ access(Editor)
+ fun addCreature(creature: @Creature) {
+ pre {
+ self.borrowNameToUIDDictionary(LibraryCategory.CREATURE)[creature.name] == nil:
+ "Creature name already exists"
+ }
+ let uuid = creature.uuid
+ let name = creature.name
+ let tags = creature.tags
+ self.setNameAndTags(LibraryCategory.CREATURE, uuid, name, tags)
+
+ self.creatures[uuid] <-! creature
+
+ emit LibraryEntryAdded(
+ self.owner?.address ?? panic("Owner not found"),
+ LibraryCategory.CREATURE.rawValue,
+ uuid,
+ name,
+ tags
+ )
+ }
+
+ access(Editor)
+ fun removeEntry(_ category: LibraryCategory, _ uuid: UInt64) {
+ self.removeNameAndTags(category, uuid)
+
+ switch category {
+ case LibraryCategory.OBJECT:
+ Burner.burn(<- self.objects.remove(key: uuid))
+ case LibraryCategory.ITEM:
+ Burner.burn(<- self.items.remove(key: uuid))
+ case LibraryCategory.ABILITY:
+ Burner.burn(<- self.abilities.remove(key: uuid))
+ case LibraryCategory.SHAPE:
+ Burner.burn(<- self.shapes.remove(key: uuid))
+ case LibraryCategory.FEATURE:
+ Burner.burn(<- self.features.remove(key: uuid))
+ case LibraryCategory.CREATURE:
+ Burner.burn(<- self.creatures.remove(key: uuid))
+ default:
+ panic("Invalid category")
+ }
+
+ emit LibraryEntryRemoved(
+ self.owner?.address ?? panic("Owner not found"),
+ category.rawValue,
+ uuid
+ )
+ }
+
+ // -------- Public Functions --------
+
+ // Get the uids of the entries in the library
+ access(all) view
+ fun getTagUIDs(_ category: LibraryCategory, _ tag: String): [UInt64] {
+ let tagToUIDs = self.borrowTagToUIDsDictionary(category)
+ if let uids = tagToUIDs[tag] {
+ return *uids
+ }
+ return []
+ }
+
+ // Get the length of the entries in the library
+ access(all) view
+ fun getEntryLength(_ category: LibraryCategory): Int {
+ switch category {
+ case LibraryCategory.OBJECT:
+ return self.objects.length
+ case LibraryCategory.ITEM:
+ return self.items.length
+ case LibraryCategory.ABILITY:
+ return self.abilities.length
+ case LibraryCategory.SHAPE:
+ return self.shapes.length
+ case LibraryCategory.FEATURE:
+ return self.features.length
+ case LibraryCategory.CREATURE:
+ return self.creatures.length
+ default:
+ panic("Invalid category")
+ }
+ }
+
+ access(all) view
+ fun getAllEntryUIDs(_ category: LibraryCategory): [UInt64] {
+ switch category {
+ case LibraryCategory.OBJECT:
+ return self.objects.keys
+ case LibraryCategory.ITEM:
+ return self.items.keys
+ case LibraryCategory.ABILITY:
+ return self.abilities.keys
+ case LibraryCategory.SHAPE:
+ return self.shapes.keys
+ case LibraryCategory.FEATURE:
+ return self.features.keys
+ case LibraryCategory.CREATURE:
+ return self.creatures.keys
+ default:
+ panic("Invalid category")
+ }
+ }
+
+ // Get the entry by uuid
+ access(all) view
+ fun borrowObject(_ uuid: UInt64): &Object? {
+ return &self.objects[uuid]
+ }
+
+ access(all) view
+ fun borrowObjectByName(_ name: String): &Object? {
+ if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.OBJECT)[name] {
+ return self.borrowObject(uuid)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowItem(_ uuid: UInt64): &Item? {
+ return &self.items[uuid]
+ }
+
+ access(all) view
+ fun borrowItemByName(_ name: String): &Item? {
+ if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.ITEM)[name] {
+ return self.borrowItem(uuid)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowAbility(_ uuid: UInt64): &Ability? {
+ return &self.abilities[uuid]
+ }
+
+ access(all) view
+ fun borrowAbilityByName(_ name: String): &Ability? {
+ if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.ABILITY)[name] {
+ return self.borrowAbility(uuid)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowShape(_ uuid: UInt64): &Shape? {
+ return &self.shapes[uuid]
+ }
+
+ access(all) view
+ fun borrowShapeByName(_ name: String): &Shape? {
+ if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.SHAPE)[name] {
+ return self.borrowShape(uuid)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowFeature(_ uuid: UInt64): &Feature? {
+ return &self.features[uuid]
+ }
+
+ access(all) view
+ fun borrowFeatureByName(_ name: String): &Feature? {
+ if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.FEATURE)[name] {
+ return self.borrowFeature(uuid)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowCreature(_ uuid: UInt64): &Creature? {
+ return &self.creatures[uuid]
+ }
+
+ access(all) view
+ fun borrowCreatureByName(_ name: String): &Creature? {
+ if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.CREATURE)[name] {
+ return self.borrowCreature(uuid)
+ }
+ return nil
+ }
+
+ // -------- Restricted Functions --------
+
+ // Borrow a writable object by uuid
+ access(contract) view
+ fun borrowWritableObject(_ uuid: UInt64): auth(Editor) &Object? {
+ return &self.objects[uuid]
+ }
+
+ // Borrow a writable item by uuid
+ access(contract) view
+ fun borrowWritableItem(_ uuid: UInt64): auth(Editor) &Item? {
+ return &self.items[uuid]
+ }
+
+ // Borrow a writable ability by uuid
+ access(contract) view
+ fun borrowWritableAbility(_ uuid: UInt64): auth(Editor) &Ability? {
+ return &self.abilities[uuid]
+ }
+
+ // Borrow a writable shape by uuid
+ access(contract) view
+ fun borrowWritableShape(_ uuid: UInt64): auth(Editor) &Shape? {
+ return &self.shapes[uuid]
+ }
+
+ // Borrow a writable feature by uuid
+ access(contract) view
+ fun borrowWritableFeature(_ uuid: UInt64): auth(Editor) &Feature? {
+ return &self.features[uuid]
+ }
+
+ // Borrow a writable creature by uuid
+ access(contract) view
+ fun borrowWritableCreature(_ uuid: UInt64): auth(Editor) &Creature? {
+ return &self.creatures[uuid]
+ }
+
+ // -------- Private Functions --------
+
+ access(self) view
+ fun borrowNameToUIDDictionary(_ category: LibraryCategory): auth(Mutate) &{String: UInt64} {
+ return &self.nameToUID[category] as auth(Mutate) &{String: UInt64}?
+ ?? panic("Name to UID dictionary not found")
+ }
+
+ access(self) view
+ fun borrowTagToUIDsDictionary(_ category: LibraryCategory): auth(Mutate) &{String: [UInt64]} {
+ return &self.tagToUIDs[category] as auth(Mutate) &{String: [UInt64]}?
+ ?? panic("Tag to UID dictionary not found")
+ }
+
+ access(self)
+ fun borrowNamable(_ category: LibraryCategory, _ uuid: UInt64): &{Nameable}? {
+ switch category {
+ case LibraryCategory.OBJECT:
+ return self.borrowObject(uuid)
+ case LibraryCategory.ITEM:
+ return self.borrowItem(uuid)
+ case LibraryCategory.ABILITY:
+ return self.borrowAbility(uuid)
+ case LibraryCategory.SHAPE:
+ return self.borrowShape(uuid)
+ case LibraryCategory.FEATURE:
+ return self.borrowFeature(uuid)
+ case LibraryCategory.CREATURE:
+ return self.borrowCreature(uuid)
+ default:
+ return nil
+ }
+ }
+
+ access(self)
+ fun setNameAndTags(_ category: LibraryCategory, _ uuid: UInt64, _ name: String, _ tags: [String]) {
+ let nameToUID = self.borrowNameToUIDDictionary(category)
+ let tagToUIDs = self.borrowTagToUIDsDictionary(category)
+
+ // set the name to the uuid
+ nameToUID[name] = uuid
+
+ // set the tags to the uuid
+ for tag in tags {
+ if tagToUIDs[tag] == nil {
+ tagToUIDs[tag] = [uuid]
+ } else {
+ tagToUIDs[tag]!.append(uuid)
+ }
+ }
+ }
+
+ access(self)
+ fun removeNameAndTags(_ category: LibraryCategory, _ uuid: UInt64) {
+ let nameToUID = self.borrowNameToUIDDictionary(category)
+ let tagToUIDs = self.borrowTagToUIDsDictionary(category)
+
+ if let namable = self.borrowNamable(category, uuid) {
+ let _ = nameToUID.remove(key: namable.name)
+ for tag in namable.tags {
+ if let tagArr = tagToUIDs[tag] {
+ if let idIndex = tagArr.firstIndex(of: uuid) {
+ let _ = tagArr.remove(at: idIndex)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // ------------ Library Entities ------------
+
+ access(all) struct interface Copyable {
+ access(all) fun copy(): {Copyable}
+ }
+
+ access(all) struct Potentiality: Copyable {
+ access(all) var initial: Int64
+
+ view init(
+ initial: Int64,
+ ) {
+ self.initial = initial
+ }
+
+ access(all) fun copy(): {Copyable} { return self }
+
+ access(contract)
+ fun add(_ amount: Int64) {
+ post {
+ self.initial >= 0: "Initial potentiality cannot be negative"
+ }
+ self.initial = self.initial + amount
+ }
+ }
+
+ // The basic attributes of a character
+ access(all) struct Attributes: Copyable {
+ access(all) var strength: Int64
+ access(all) var vitality: Int64
+ access(all) var spirit: Int64
+
+ view init(strength: Int64, vitality: Int64, spirit: Int64) {
+ self.strength = strength
+ self.vitality = vitality
+ self.spirit = spirit
+ }
+
+ access(all) fun copy(): {Copyable} { return self }
+
+ access(all) view
+ fun getValue(_ type: AttributeType): Int64 {
+ switch type {
+ case AttributeType.STRENGTH:
+ return self.strength
+ case AttributeType.VITALITY:
+ return self.vitality
+ case AttributeType.SPIRIT:
+ return self.spirit
+ default:
+ return 0
+ }
+ }
+
+ /// This method will not check if the value is negative
+ access(contract)
+ fun setValue(_ type: AttributeType, _ value: Int64) {
+ switch type {
+ case AttributeType.STRENGTH:
+ self.strength = value
+ case AttributeType.VITALITY:
+ self.vitality = value
+ case AttributeType.SPIRIT:
+ self.spirit = value
+ default:
+ panic("Invalid attribute type")
+ }
+ }
+
+ // For this method, the final value cannot be negative
+ access(contract)
+ fun addValue(_ type: AttributeType, _ value: Int64) {
+ post {
+ self.strength >= 0: "Strength cannot be negative"
+ self.vitality >= 0: "Vitality cannot be negative"
+ self.spirit >= 0: "Spirit cannot be negative"
+ }
+ switch type {
+ case AttributeType.STRENGTH:
+ self.strength = self.strength + value
+ if self.strength < 0 {
+ self.strength = 0
+ }
+ case AttributeType.VITALITY:
+ self.vitality = self.vitality + value
+ if self.vitality < 0 {
+ self.vitality = 0
+ }
+ case AttributeType.SPIRIT:
+ self.spirit = self.spirit + value
+ if self.spirit < 0 {
+ self.spirit = 0
+ }
+ default:
+ panic("Invalid attribute type")
+ }
+ }
+ }
+
+ access(all) struct Defence: Copyable {
+ access(all) var physical: Int64
+ access(all) var endurance: Int64
+ access(all) var resistance: Int64
+
+ view init(physical: Int64, endurance: Int64, resistance: Int64) {
+ self.physical = physical
+ self.endurance = endurance
+ self.resistance = resistance
+ }
+
+ access(all) fun copy(): {Copyable} { return self }
+
+ access(all) view
+ fun getValue(_ type: DefenceType): Int64 {
+ switch type {
+ case DefenceType.PHYSICAL:
+ return self.physical
+ case DefenceType.ENDURANCE:
+ return self.endurance
+ case DefenceType.RESISTANCE:
+ return self.resistance
+ default:
+ return 0
+ }
+ }
+
+ access(all) view
+ fun getDefenceFrom(_ type: AttackType): Int64 {
+ switch type {
+ case AttackType.PHYSICAL:
+ return self.physical
+ case AttackType.EROSION:
+ return self.endurance
+ case AttackType.OCCULT:
+ return self.resistance
+ case AttackType.TRUE_DAMAGE:
+ return 0
+ default:
+ return 0
+ }
+ }
+
+ // This method will not check if the value is negative
+ access(contract)
+ fun setValue(_ type: DefenceType, _ value: Int64) {
+ switch type {
+ case DefenceType.PHYSICAL:
+ self.physical = value
+ case DefenceType.ENDURANCE:
+ self.endurance = value
+ case DefenceType.RESISTANCE:
+ self.resistance = value
+ default:
+ panic("Invalid defence type")
+ }
+ }
+ }
+
+ // The status of the unit
+ access(all) struct UnitStatus: Copyable {
+ access(all) var attributes: Attributes
+ access(all) var defence: Defence
+ access(all) var potentiality: Potentiality
+
+ access(all) var slotsOccupied: {EquipSlot: [String]}
+
+ view init(
+ attributes: Attributes,
+ defence: Defence,
+ potentiality: Potentiality
+ ) {
+ self.attributes = attributes
+ self.defence = defence
+ self.potentiality = potentiality
+ self.slotsOccupied = {}
+ }
+
+ access(all) fun copy(): {Copyable} { return self }
+
+ access(contract)
+ fun setAttributes(_ attributes: Attributes) {
+ self.attributes = attributes
+ }
+
+ access(contract)
+ fun setDefence(_ defence: Defence) {
+ self.defence = defence
+ }
+
+ access(contract)
+ fun setPotentiality(_ potentiality: Potentiality) {
+ self.potentiality = potentiality
+ }
+
+ access(contract) view
+ fun borrowWritableSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} {
+ return &self.slotsOccupied
+ }
+
+ access(all) view
+ fun borrowAttributes(): &Attributes {
+ return &self.attributes
+ }
+
+ access(all) view
+ fun borrowDefence(): &Defence {
+ return &self.defence
+ }
+
+ access(all) view
+ fun borrowPotentiality(): &Potentiality {
+ return &self.potentiality
+ }
+ }
+
+ access(all) resource interface Nameable {
+ access(all) let name: String
+ access(all) let tags: [String]
+
+ access(all) view
+ fun hasAnyTag(): Bool {
+ return self.tags.length > 0
+ }
+
+ access(all) view
+ fun withTag(_ tag: String): Bool {
+ return self.tags.contains(tag)
+ }
+ }
+
+ access(all) resource interface ValueCarrier {
+ // The value of the item
+ access(all) var value: UFix64?
+
+ access(all) view
+ fun hasValue(): Bool {
+ return self.value != nil
+ }
+
+ access(Editor)
+ fun setValue(value: UFix64) {
+ self.value = value
+ }
+
+ access(Editor)
+ fun addValue(value: UFix64) {
+ post {
+ (self.value ?? 0.0) >= 0.0: "Value cannot be negative"
+ }
+ self.value = (self.value ?? 0.0) + value
+ }
+ }
+
+ access(all) resource interface EffectsCarrier {
+ access(all) let effects: [String]
+
+ access(all) view
+ fun hasEffects(): Bool {
+ return self.effects.length > 0
+ }
+
+ access(Editor)
+ fun addEffect(effect: String) {
+ self.effects.append(effect)
+ }
+
+ access(Editor)
+ fun removeEffect(effect: String) {
+ if let index = self.effects.firstIndex(of: effect) {
+ let _ = self.effects.remove(at: index)
+ }
+ }
+ }
+
+ // The AttributeCarrier resource interface is used to get the attributes of the entry.
+ access(all) resource interface AttributeCarrier {
+ access(all) view fun borrowAttributes(): &Attributes?
+
+ access(all) view fun hasAttributes(): Bool {
+ return self.borrowAttributes() != nil
+ }
+
+ access(all) view fun getAttrStr(): Int64 {
+ return self.borrowAttributes()?.strength ?? 0
+ }
+
+ access(all) view fun getAttrVit(): Int64 {
+ return self.borrowAttributes()?.vitality ?? 0
+ }
+
+ access(all) view fun getAttrSpir(): Int64 {
+ return self.borrowAttributes()?.spirit ?? 0
+ }
+ }
+
+ // The DefenceCarrier resource interface is used to get the defence of the entry.
+ access(all) resource interface DefenceCarrier {
+ access(all) view fun borrowDefence(): &Defence?
+
+ access(all) view fun hasDefence(): Bool {
+ return self.borrowDefence() != nil
+ }
+
+ access(all) view fun getDefPhys(): Int64 {
+ return self.borrowDefence()?.physical ?? 0
+ }
+
+ access(all) view fun getDefEnd(): Int64 {
+ return self.borrowDefence()?.endurance ?? 0
+ }
+
+ access(all) view fun getDefRes(): Int64 {
+ return self.borrowDefence()?.resistance ?? 0
+ }
+ }
+
+ // The PotentialityCarrier resource interface is used to get the potentiality of the entry.
+ access(all) resource interface PotentialityCarrier {
+ access(all) view fun borrowPotentiality(): &Potentiality?
+
+ access(all) view fun hasPotentiality(): Bool {
+ return self.borrowPotentiality() != nil
+ }
+
+ access(all) view fun getInitialPotentiality(): Int64 {
+ return self.borrowPotentiality()?.initial ?? 0
+ }
+ }
+
+ // The OptionalStatusCarrier resource interface is used to get the optional status of the entry.
+ access(all) resource interface OptionalStatusCarrier: AttributeCarrier, DefenceCarrier, PotentialityCarrier {
+ access(all) let attributes: Attributes?
+ access(all) let defence: Defence?
+ access(all) let potentiality: Potentiality?
+
+ access(all) view fun borrowAttributes(): &Attributes? {
+ return &self.attributes
+ }
+
+ access(all) view fun borrowDefence(): &Defence? {
+ return &self.defence
+ }
+
+ access(all) view fun borrowPotentiality(): &Potentiality? {
+ return &self.potentiality
+ }
+ }
+
+ // The LiveUnitStatusCarrier resource interface is used to get the live status of the entry.
+ access(all) resource interface LiveUnitStatusCarrier: AttributeCarrier, DefenceCarrier, PotentialityCarrier {
+ access(all) view fun borrowStatus(): &UnitStatus
+
+ access(all) view fun borrowAttributes(): &Attributes? {
+ return self.borrowStatus().borrowAttributes()
+ }
+
+ access(all) view fun borrowDefence(): &Defence? {
+ return self.borrowStatus().borrowDefence()
+ }
+
+ access(all) view fun borrowPotentiality(): &Potentiality? {
+ return self.borrowStatus().borrowPotentiality()
+ }
+ }
+
+ access(all) resource interface ComposableUnitStatusCarrier: LiveUnitStatusCarrier {
+ access(all) view fun borrowStatus(): &UnitStatus
+
+ access(all) fun borrowAttributesElements(): [&Attributes]
+ access(all) fun borrowDefenceElements(): [&Defence]
+ access(all) fun borrowPotentialityElements(): [&Potentiality]
+
+ access(Host)
+ fun applyStatus(_ isLast: Bool) {
+ // during initializing, we don't need to apply the status if it's not the last one
+ if self.owner == nil && !isLast {
+ return
+ }
+
+ let status = self.borrowStatus()
+
+ // Calculate the new attributes of the unit
+ let attributes = self.borrowAttributesElements()
+ let newAttributes = Attributes(strength: 0, vitality: 0, spirit: 0)
+ for attribute in attributes {
+ newAttributes.setValue(AttributeType.STRENGTH, newAttributes.strength + attribute.strength)
+ newAttributes.setValue(AttributeType.VITALITY, newAttributes.vitality + attribute.vitality)
+ newAttributes.setValue(AttributeType.SPIRIT, newAttributes.spirit + attribute.spirit)
+ }
+ status.setAttributes(newAttributes)
+
+ // Calculate the new defence of the unit
+ let defence = self.borrowDefenceElements()
+ let newDefence = Defence(physical: 0, endurance: 0, resistance: 0)
+ for one in defence {
+ newDefence.setValue(DefenceType.PHYSICAL, newDefence.physical + one.physical)
+ newDefence.setValue(DefenceType.ENDURANCE, newDefence.endurance + one.endurance)
+ newDefence.setValue(DefenceType.RESISTANCE, newDefence.resistance + one.resistance)
+ }
+ status.setDefence(newDefence)
+
+ // Calculate the new potentiality of the unit
+ let potentiality = self.borrowPotentialityElements()
+ let newPotentiality = Potentiality(initial: 0)
+ for one in potentiality {
+ newPotentiality.add(one.initial)
+ }
+ status.setPotentiality(newPotentiality)
+
+ // Emit the event
+ emit UnitStatusApplied(
+ self.uuid,
+ status.attributes.copy() as! Attributes,
+ status.defence.copy() as! Defence,
+ status.potentiality.copy() as! Potentiality
+ )
+ }
+ }
+
+ // The EntryIdentifier resource is used to identify the entry.
+ access(all) struct EntryIdentifier {
+ access(all) let library: Address
+ access(all) let category: LibraryCategory
+ access(all) let id: UInt64
+
+ view init(library: Address, category: LibraryCategory, id: UInt64) {
+ pre {
+ FGameMishal.borrowLibrary(library) != nil: "It should be a valid library."
+ }
+ self.library = library
+ self.category = category
+ self.id = id
+ }
+
+ access(all) view
+ fun getStringID(): String {
+ return self.library.toString().concat("-").concat(self.category.rawValue.toString()).concat("-").concat(self.id.toString())
+ }
+
+ access(all) view
+ fun clone(): EntryIdentifier {
+ return self
+ }
+
+ access(all) view
+ fun verify(_ type: LibraryCategory): Bool {
+ if type != self.category {
+ return false
+ }
+ switch self.category {
+ case LibraryCategory.OBJECT:
+ return self.borrowObject() != nil
+ case LibraryCategory.ITEM:
+ return self.borrowItem() != nil
+ case LibraryCategory.ABILITY:
+ return self.borrowAbility() != nil
+ case LibraryCategory.SHAPE:
+ return self.borrowShape() != nil
+ case LibraryCategory.FEATURE:
+ return self.borrowFeature() != nil
+ case LibraryCategory.CREATURE:
+ return self.borrowCreature() != nil
+ default:
+ return false
+ }
+ }
+
+ access(all) view
+ fun borrowObject(): &Object? {
+ if self.category == LibraryCategory.OBJECT {
+ return self.borrowLibrary().borrowObject(self.id)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowItem(): &Item? {
+ if self.category == LibraryCategory.ITEM {
+ return self.borrowLibrary().borrowItem(self.id)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowAbility(): &Ability? {
+ if self.category == LibraryCategory.ABILITY {
+ return self.borrowLibrary().borrowAbility(self.id)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowShape(): &Shape? {
+ if self.category == LibraryCategory.SHAPE {
+ return self.borrowLibrary().borrowShape(self.id)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowFeature(): &Feature? {
+ if self.category == LibraryCategory.FEATURE {
+ return self.borrowLibrary().borrowFeature(self.id)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowCreature(): &Creature? {
+ if self.category == LibraryCategory.CREATURE {
+ return self.borrowLibrary().borrowCreature(self.id)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun borrowLibrary(): &Library {
+ return FGameMishal.borrowLibrary(self.library) ?? panic("Library not found")
+ }
+ }
+
+ // The FungibleEntry resource is used to store the fungible entry.
+ access(all) resource FungibleEntry: FungibleToken.Vault {
+ access(all) let identifier: EntryIdentifier
+ access(all) var balance: UFix64
+
+ view init(identifier: EntryIdentifier, amount: UFix64) {
+ self.identifier = identifier
+ self.balance = amount
+ }
+
+ access(all) view fun getCount(): UInt64 {
+ return UInt64(self.balance)
+ }
+
+ access(all) view fun getViews(): [Type] {
+ return []
+ }
+
+ access(all) fun resolveView(_ view: Type): AnyStruct? {
+ return nil
+ }
+
+ access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
+ return amount <= self.balance
+ }
+
+ access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @FungibleEntry {
+ self.balance = self.balance - amount
+ return <-create FungibleEntry(identifier: self.identifier, amount: amount)
+ }
+
+ access(all) fun deposit(from: @{FungibleToken.Vault}) {
+ let vault <- from as! @FungibleEntry
+ self.balance = self.balance + vault.balance
+ destroy vault
+ }
+
+ access(all) fun createEmptyVault(): @FungibleEntry {
+ return <-create FungibleEntry(identifier: self.identifier, amount: 0.0)
+ }
+ }
+
+ // The EntryContainer resource interface is used to borrow the entry by ID.
+ access(all) resource interface EntryContainer {
+ access(all) view
+ fun borrowEntryByID(_ id: String): &FungibleEntry?
+
+ access(contract)
+ fun deposit(entry: @FungibleEntry)
+
+ access(contract)
+ fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry
+ }
+
+ // Check if the entry is uniqueness
+ access(all) view
+ fun isEntryUniqueness(_ category: LibraryCategory): Bool {
+ switch category {
+ case LibraryCategory.OBJECT:
+ return false
+ case LibraryCategory.ITEM:
+ return false
+ default:
+ return true
+ }
+ }
+
+ // The EntryCollection resource is used to store the entries.
+ access(all) resource EntryCollection: EntryContainer {
+ access(all) let entries: @{String: FungibleEntry}
+ access(all) let categories: {LibraryCategory: [String]}
+
+ view init() {
+ self.entries <- {}
+ self.categories = {}
+ }
+
+ access(all) view fun getLength(): Int {
+ return self.entries.length
+ }
+
+ access(all) view fun getLengthByCategory(_ category: LibraryCategory): Int {
+ return self.categories[category]?.length ?? 0
+ }
+
+ access(all)
+ fun getEntryIdentifiers(_ category: LibraryCategory?): [EntryIdentifier] {
+ let ret: [EntryIdentifier] = []
+ let keys = category == nil ? self.entries.keys : self.getKeysByCategory(category!)
+ for id in keys {
+ if let ref = self.borrowEntryByID(id) {
+ ret.append(ref.identifier.clone())
+ }
+ }
+ return ret
+ }
+
+ access(all)
+ fun borrowEntries(_ category: LibraryCategory?): [&FungibleEntry] {
+ let ret: [&FungibleEntry] = []
+ let keys = category == nil ? self.entries.keys : self.getKeysByCategory(category!)
+ for id in keys {
+ if let ref = self.borrowEntryByID(id) {
+ ret.append(ref)
+ }
+ }
+ return ret
+ }
+
+ access(all) view
+ fun getKeysByCategory(_ category: LibraryCategory): [String] {
+ return self.categories[category] ?? []
+ }
+
+ access(all) view
+ fun borrowEntryByID(_ id: String): &FungibleEntry? {
+ return &self.entries[id]
+ }
+
+ access(contract)
+ fun deposit(entry: @FungibleEntry) {
+ pre {
+ emit EntryDeposited(
+ entry.identifier.library,
+ entry.identifier.category.rawValue,
+ entry.identifier.id,
+ entry.balance,
+ self.owner?.address,
+ entryUUID: entry.uuid,
+ collectionUUID: self.uuid
+ )
+ }
+ let uid = entry.identifier.getStringID()
+
+ if FGameMishal.isEntryUniqueness(entry.identifier.category) {
+ assert(entry.balance == 1.0 && self.entries[uid] == nil, message: "Non-fungible entry must have a balance of 1.0")
+ }
+
+ if let oldRef = self.borrowEntryByID(uid) {
+ oldRef.deposit(from: <- entry)
+ } else {
+ self.entries[uid] <-! entry
+ }
+ }
+
+ access(contract)
+ fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry {
+ post {
+ result.identifier.getStringID() == id: "The ID of the withdrawn token must be the same as the requested ID"
+ emit EntryWithdrawn(
+ result.identifier.library,
+ result.identifier.category.rawValue,
+ result.identifier.id,
+ result.balance,
+ self.owner?.address,
+ entryUUID: result.uuid,
+ collectionUUID: self.uuid
+ )
+ }
+ let ref = self.borrowEntryByID(id)
+ ?? panic("EntryCollection.withdraw: Could not withdraw an entry with ID ".concat(id).concat(". Check if the entry exists."))
+ if FGameMishal.isEntryUniqueness(ref.identifier.category) {
+ assert(amount == nil, message: "Non-fungible entry cannot have an amount")
+ return <- self.entries.remove(key: id)!
+ } else {
+ assert(amount != nil, message: "Fungible entry must have an amount")
+ assert(
+ ref.isAvailableToWithdraw(amount: amount!),
+ message: "EntryCollection.withdraw: The entry is not available to withdraw, amount: "
+ .concat(amount!.toString())
+ .concat(", available: ")
+ .concat(ref.balance.toString())
+ )
+ return <- ref.withdraw(amount: amount!)
+ }
+ }
+
+ access(Host | Editor) view
+ fun borrowEditableEntry(_ id: String): auth(FungibleToken.Withdraw) &FungibleEntry? {
+ return &self.entries[id]
+ }
+ }
+
+ // The Object resource represents a static, non-playable object in the game
+ access(all) resource Object: DefenceCarrier, ValueCarrier, Nameable {
+ access(all) let name: String
+ access(all) let tags: [String]
+ // The defence of the character
+ access(all) let defence: Defence
+ // The value of the object
+ access(all) var value: UFix64?
+
+ view init(
+ name: String,
+ tags: [String],
+ defence: Defence,
+ value: UFix64?
+ ) {
+ self.name = name
+ self.tags = tags
+ self.defence = defence
+ self.value = value
+ }
+
+ access(all) view fun borrowDefence(): &Defence? {
+ return &self.defence as &Defence
+ }
+ }
+
+ // Method to create an object
+ access(all) view
+ fun createObject(
+ name: String,
+ tags: [String],
+ defence: Defence,
+ value: UFix64?
+ ): @Object {
+ return <-create Object(name: name, tags: tags, defence: defence, value: value)
+ }
+
+ access(all) resource Item: OptionalStatusCarrier, ValueCarrier, EffectsCarrier, Nameable {
+ access(all) let name: String
+ access(all) let tags: [String]
+ // The value of the item
+ access(all) var value: UFix64?
+ // Main attributes of the character
+ access(all) let attributes: Attributes?
+ // The defence of the character
+ access(all) let defence: Defence?
+ // The potentiality of the item
+ access(all) let potentiality: Potentiality?
+ // The effects of the item
+ access(all) let effects: [String]
+ // The slots occupied by the item
+ access(all) let slotsOccupied: {EquipSlot: UInt8}
+ // The slots provided by the item
+ access(all) let slotsProvided: {EquipSlot: UInt8}
+
+ view init(
+ name: String,
+ tags: [String],
+ value: UFix64?,
+ attributes: Attributes?,
+ defence: Defence?,
+ potentiality: Potentiality?,
+ effects: [String],
+ slotsOccupied: {EquipSlot: UInt8},
+ slotsProvided: {EquipSlot: UInt8},
+ ) {
+ self.name = name
+ self.tags = tags
+ self.value = value
+ self.attributes = attributes
+ self.defence = defence
+ self.potentiality = potentiality
+ self.effects = effects
+ self.slotsOccupied = slotsOccupied
+ self.slotsProvided = slotsProvided
+ }
+
+ access(all) view
+ fun isEquippable(): Bool {
+ return self.slotsOccupied.length > 0
+ }
+
+ access(all) view
+ fun isProvidedSlots(): Bool {
+ return self.slotsProvided.length > 0
+ }
+ }
+
+ // Method to create an item
+ access(all) view
+ fun createItem(
+ name: String,
+ tags: [String],
+ value: UFix64?,
+ attributes: Attributes?,
+ defence: Defence?,
+ potentiality: Potentiality?,
+ effects: [String],
+ slotsOccupied: {EquipSlot: UInt8},
+ slotsProvided: {EquipSlot: UInt8},
+ ): @Item {
+ return <-create Item(name: name, tags: tags, value: value, attributes: attributes, defence: defence, potentiality: potentiality, effects: effects, slotsOccupied: slotsOccupied, slotsProvided: slotsProvided)
+ }
+
+ access(all) resource Ability: AttributeCarrier, DefenceCarrier, EffectsCarrier, Nameable {
+ access(all) let name: String
+ access(all) let tags: [String]
+ access(all) let level: UInt64
+ access(all) let occupy: AttributeType?
+ access(all) let effects: [String]
+ access(all) let attributes: Attributes?
+ access(all) let defence: Defence?
+
+ view init(
+ level: UInt64,
+ name: String,
+ tags: [String],
+ occupy: AttributeType?,
+ effects: [String],
+ attributes: Attributes?,
+ defence: Defence?
+ ) {
+ self.name = name
+ self.tags = tags
+ self.level = level
+ self.occupy = occupy
+ self.effects = effects
+ self.attributes = attributes
+ self.defence = defence
+ }
+
+ access(all) view fun borrowAttributes(): &Attributes? {
+ return &self.attributes
+ }
+
+ access(all) view fun borrowDefence(): &Defence? {
+ return &self.defence
+ }
+ }
+
+ // Method to create an ability
+ access(all) view
+ fun createAbility(
+ name: String,
+ tags: [String],
+ level: UInt64,
+ occupy: AttributeType?,
+ effects: [String],
+ attributes: Attributes?,
+ defence: Defence?
+ ): @Ability {
+ return <-create Ability(level: level, name: name, tags: tags, occupy: occupy, effects: effects, attributes: attributes, defence: defence)
+ }
+
+ // The CreatureSettingsCarrier resource interface is used to get the settings of the creature.
+ access(all) resource interface CreatureSettingsCarrier {
+ access(contract) view
+ fun borrowWritableSettings(): auth(Mutate) &{CreatureSettings: Int64}
+
+ access(all) view
+ fun getSetting(_ setting: CreatureSettings): Int64? {
+ let settings = self.borrowWritableSettings()
+ return settings[setting]
+ }
+
+ access(all) view
+ fun hasSettings(): Bool {
+ let settings = self.borrowWritableSettings()
+ return settings.length > 0
+ }
+
+ access(Host | Editor)
+ fun updateSetting(_ setting: CreatureSettings, _ value: Int64) {
+ let settings = self.borrowWritableSettings()
+ settings[setting] = value
+
+ emit CreatureSettingUpdated(
+ self.getType().identifier,
+ self.uuid,
+ setting.rawValue,
+ value
+ )
+ }
+ }
+
+ access(all) resource interface SettingsUnit: CreatureSettingsCarrier {
+ access(all) let settings: {CreatureSettings: Int64}
+
+ access(contract) view
+ fun borrowWritableSettings(): auth(Mutate) &{CreatureSettings: Int64} {
+ return &self.settings
+ }
+ }
+
+ access(all) resource Shape: SettingsUnit, Nameable {
+ access(all) let name: String
+ access(all) let tags: [String]
+ access(all) let settings: {CreatureSettings: Int64}
+ access(all) let slotsAvailable: {EquipSlot: UInt8}
+
+ view init(
+ name: String,
+ tags: [String],
+ bodySize: Int64,
+ occupyRange: Int64,
+ moveSpeed: Int64,
+ perceptionRange: Int64,
+ slotsAvailable: {EquipSlot: UInt8 }
+ ) {
+ self.name = name
+ self.tags = tags
+ self.settings = {}
+ self.slotsAvailable = slotsAvailable
+
+ self.settings[CreatureSettings.SIZE] = bodySize
+ self.settings[CreatureSettings.MOVE_SPEED] = moveSpeed
+ self.settings[CreatureSettings.PERCEPTION_RANGE] = perceptionRange
+ self.settings[CreatureSettings.OCCUPY_RANGE] = occupyRange
+ }
+ }
+
+ // Method to create a shape
+ access(all) view
+ fun createShape(
+ name: String,
+ tags: [String],
+ bodySize: Int64,
+ occupyRange: Int64,
+ moveSpeed: Int64,
+ perceptionRange: Int64,
+ slotsAvailable: {EquipSlot: UInt8}
+ ): @Shape {
+ return <-create Shape(name: name, tags: tags, bodySize: bodySize, occupyRange: occupyRange, moveSpeed: moveSpeed, perceptionRange: perceptionRange, slotsAvailable: slotsAvailable)
+ }
+
+ // The ShapeCarrier resource interface is used to get the shape of the creature.
+ access(all) resource interface ShapeCarrier: CreatureSettingsCarrier, EntryContainer {
+ access(all) view fun getShapeIdentifier(): EntryIdentifier?
+
+ access(all) view
+ fun borrowShape(): &Shape? {
+ if let shape = self.getShapeIdentifier() {
+ return shape.borrowShape()
+ }
+ return nil
+ }
+
+ access(all) view
+ fun hasShape(): Bool {
+ return self.getShapeIdentifier() != nil
+ }
+
+ access(all) view
+ fun getGender(): Int64? {
+ let settings = self.borrowWritableSettings()
+ if let gender = settings[CreatureSettings.GENDER] {
+ return gender
+ }
+ if let shape = self.borrowShape() {
+ return shape.getSetting(CreatureSettings.GENDER)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun getForm(): Int64? {
+ let settings = self.borrowWritableSettings()
+ if let form = settings[CreatureSettings.FORM] {
+ return form
+ }
+ if let shape = self.borrowShape() {
+ return shape.getSetting(CreatureSettings.FORM)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun getSize(): Int64? {
+ let settings = self.borrowWritableSettings()
+ if let size = settings[CreatureSettings.SIZE] {
+ return size
+ }
+ if let shape = self.borrowShape() {
+ return shape.getSetting(CreatureSettings.SIZE)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun getMoveSpeed(): Int64? {
+ let settings = self.borrowWritableSettings()
+ if let moveSpeed = settings[CreatureSettings.MOVE_SPEED] {
+ return moveSpeed
+ }
+ if let shape = self.borrowShape() {
+ return shape.getSetting(CreatureSettings.MOVE_SPEED)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun getPerceptionRange(): Int64? {
+ let settings = self.borrowWritableSettings()
+ if let perceptionRange = settings[CreatureSettings.PERCEPTION_RANGE] {
+ return perceptionRange
+ }
+ if let shape = self.borrowShape() {
+ return shape.getSetting(CreatureSettings.PERCEPTION_RANGE)
+ }
+ return nil
+ }
+
+ access(all) view
+ fun getOccupyRange(): Int64? {
+ let settings = self.borrowWritableSettings()
+ if let occupyRange = settings[CreatureSettings.OCCUPY_RANGE] {
+ return occupyRange
+ }
+ if let shape = self.borrowShape() {
+ return shape.getSetting(CreatureSettings.OCCUPY_RANGE)
+ }
+ return nil
+ }
+
+ // --- Gameplay Methods ---
+
+ access(Host | Editor)
+ fun applyShape(_ shape: @FungibleEntry) {
+ pre {
+ shape.identifier.verify(LibraryCategory.SHAPE): "Shape identifier is invalid"
+ }
+ }
+ }
+
+ access(all) resource interface AbilitiesCarrier: EntryContainer {
+ access(all) view fun getAbilitiesLength(): Int
+ access(all) fun getAbilityIdentifiers(): [EntryIdentifier]
+
+ access(all)
+ fun borrowAbilities(): [&Ability] {
+ return self.getAbilityIdentifiers()
+ .map(view fun (_ x: EntryIdentifier): &Ability? {
+ return x.borrowAbility()
+ })
+ .filter(view fun (_ x: &Ability?): Bool {
+ return x != nil
+ })
+ .map(view fun (_ x: &Ability?): &Ability {
+ return x!
+ })
+ }
+
+ access(all) view
+ fun hasAbilities(): Bool {
+ return self.getAbilitiesLength() > 0
+ }
+
+ // --- Gameplay Methods ---
+
+ access(Host | Editor)
+ fun gainAbility(_ ability: @FungibleEntry) {
+ pre {
+ ability.identifier.verify(LibraryCategory.ABILITY): "Ability identifier is invalid"
+ }
+ }
+
+ access(Host | Editor)
+ fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry {
+ post {
+ result.identifier.getStringID() == ability.getStringID(): "The ID of the withdrawn token must be the same as the requested ID"
+ result.identifier.verify(LibraryCategory.ABILITY): "Entry is not an ability"
+ }
+ }
+ }
+
+ access(all) resource interface ItemsCarrier: EntryContainer {
+ access(all) view fun getItemsLength(): Int
+ access(all) fun borrowItemEntries(): [&FungibleEntry]
+
+ access(all)
+ fun borrowItems(): [&Item] {
+ let itemEntries = self.borrowItemEntries()
+
+ let ret: [&Item] = []
+ for itemEntry in itemEntries {
+ if let item = itemEntry.identifier.borrowItem() {
+ var amount = itemEntry.getCount()
+ while amount > 0 {
+ ret.append(item)
+ amount = amount - 1
+ }
+ }
+ }
+ return ret
+ }
+
+ access(all) view
+ fun hasItems(): Bool {
+ return self.getItemsLength() > 0
+ }
+
+ // --- Item Gameplay Methods ---
+
+ access(Host | Editor)
+ fun lootItem(_ entry: @FungibleEntry) {
+ pre {
+ entry.identifier.verify(LibraryCategory.ITEM): "Entry is not an item"
+ }
+ }
+
+ access(Host | Editor)
+ fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry {
+ post {
+ result.identifier.getStringID() == item.getStringID(): "The ID of the withdrawn token must be the same as the requested ID"
+ result.identifier.verify(LibraryCategory.ITEM): "Entry is not an item"
+ }
+ }
+ }
+
+ // This is a resource that can borrow a writable collection
+ access(all) resource interface CollectionContainer {
+ access(contract)
+ view fun borrowWritableCollection(): auth(Host) &EntryCollection
+
+ access(all) view
+ fun borrowReadonlyCollection(): &EntryCollection {
+ return self.borrowWritableCollection()
+ }
+ }
+
+ access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier, CollectionContainer {
+
+ // --- Implement EntryContainer ---
+
+ access(all) view fun borrowEntryByID(_ id: String): &FungibleEntry? {
+ let collection = self.borrowReadonlyCollection()
+ return collection.borrowEntryByID(id)
+ }
+
+ access(contract)
+ fun deposit(entry: @FungibleEntry) {
+ self.borrowWritableCollection().deposit(entry: <-entry)
+ }
+
+ access(contract)
+ fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry {
+ return <- self.borrowWritableCollection().withdraw(id, amount: amount)
+ }
+
+ // --- Implement AbilitiesCarrier ---
+
+ access(all) view fun getAbilitiesLength(): Int {
+ let collection = self.borrowReadonlyCollection()
+ return collection.getLengthByCategory(LibraryCategory.ABILITY)
+ }
+
+ access(all)
+ fun getAbilityIdentifiers(): [EntryIdentifier] {
+ let collection = self.borrowReadonlyCollection()
+ return collection.getEntryIdentifiers(LibraryCategory.ABILITY)
+ }
+
+ // --- Implement ItemsCarrier ---
+
+ access(all) view fun getItemsLength(): Int {
+ let collection = self.borrowReadonlyCollection()
+ return collection.getLengthByCategory(LibraryCategory.ITEM)
+ }
+
+ access(all)
+ fun borrowItemEntries(): [&FungibleEntry] {
+ let collection = self.borrowReadonlyCollection()
+ return collection.borrowEntries(LibraryCategory.ITEM)
+ }
+
+ // --- Implement ShapeCarrier ---
+
+ // Borrow the shape of the unit
+ access(all) view
+ fun getShapeIdentifier(): EntryIdentifier? {
+ let collection = self.borrowReadonlyCollection()
+ let shape = collection.getKeysByCategory(LibraryCategory.SHAPE)
+ if shape.length > 0 {
+ if let entry = collection.borrowEntryByID(shape[0]) {
+ return entry.identifier.clone()
+ }
+ }
+ return nil
+ }
+
+ // Apply a shape to the unit
+ access(Host | Editor)
+ fun applyShape(_ shape: @FungibleEntry) {
+ let collection = self.borrowReadonlyCollection()
+ let shapes = collection.getKeysByCategory(LibraryCategory.SHAPE)
+
+ // if the shape exists, remove it
+ if shapes.length > 0 {
+ for key in shapes {
+ Burner.burn(<-self.withdraw(key, amount: nil))
+ }
+ }
+
+ // deposit the new shape
+ self.deposit(entry: <-shape)
+ }
+ }
+
+ // This is a resource inteface with a collection resource stored in it
+ access(all) resource interface CollectionContainerUnit: CollectionContainer {
+ access(all) let collection: @EntryCollection
+
+ // ---- Implement UnitCollectionBaseCarrier ----
+
+ access(contract) view
+ fun borrowWritableCollection(): auth(Host) &EntryCollection {
+ return &self.collection
+ }
+ }
+
+ // This is a static collection unit that all resource are directly stored in it
+ access(all) resource interface StaticCollectionUnit: UnitCollectionBaseCarrier, CollectionContainerUnit {
+ // ---- Implement Item Gameplay Methods ----
+
+ access(Host | Editor)
+ fun lootItem(_ entry: @FungibleEntry) {
+ self.deposit(entry: <-entry)
+ }
+
+ access(Host | Editor)
+ fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry {
+ return <- self.withdraw(item.getStringID(), amount: amount)
+ }
+
+ // ---- Implement Ability Gameplay Methods ----
+
+ access(Host | Editor)
+ fun gainAbility(_ ability: @FungibleEntry) {
+ self.deposit(entry: <-ability)
+ }
+
+ access(Host | Editor)
+ fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry {
+ return <- self.withdraw(ability.getStringID(), amount: nil)
+ }
+ }
+
+ // The Feature resource is used to define the features of the entry.
+ access(all) resource Feature: Nameable, OptionalStatusCarrier, StaticCollectionUnit, EffectsCarrier, SettingsUnit {
+ access(all) let name: String
+ access(all) let tags: [String]
+ access(all) let attributes: Attributes?
+ access(all) let defence: Defence?
+ access(all) let potentiality: Potentiality?
+ access(all) let collection: @EntryCollection
+ access(all) let effects: [String]
+ access(all) let settings: {CreatureSettings: Int64}
+
+ init(
+ name: String,
+ tags: [String],
+ attributes: Attributes?,
+ defence: Defence?,
+ potentiality: Potentiality?,
+ shape: EntryIdentifier?,
+ abilities: [EntryIdentifier],
+ items: [EntryIdentifier],
+ itemAmounts: {String: UFix64},
+ effects: [String],
+ settings: {CreatureSettings: Int64}
+ ) {
+ for abilityIdentifier in abilities {
+ assert(abilityIdentifier.verify(LibraryCategory.ABILITY), message: "Ability identifier is invalid")
+ }
+ for itemIdentifier in items {
+ assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid")
+ }
+
+ self.name = name
+ self.tags = tags
+ self.attributes = attributes
+ self.defence = defence
+ self.potentiality = potentiality
+ self.effects = effects
+ self.settings = settings
+ self.collection <- create EntryCollection()
+
+ // Set the entries for shape, abilities, items
+ if shape != nil {
+ self.applyShape(<-create FungibleEntry(identifier: shape!, amount: 1.0))
+ }
+ for abilityIdentifier in abilities {
+ self.gainAbility(<-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0))
+ }
+ if items.length > 0 {
+ assert(itemAmounts.length == items.length, message: "Item amounts must be the same length as items")
+ for itemIdentifier in items {
+ let id = itemIdentifier.getStringID()
+ let amount = itemAmounts[id] ?? 0.0
+ self.lootItem(<-create FungibleEntry(identifier: itemIdentifier, amount: amount))
+ }
+ }
+ }
+ }
+
+ // Method to create a feature
+ access(all)
+ fun createFeature(
+ name: String,
+ tags: [String],
+ attributes: Attributes?,
+ defence: Defence?,
+ potentiality: Potentiality?,
+ shape: EntryIdentifier?,
+ abilities: [EntryIdentifier],
+ items: [EntryIdentifier],
+ itemAmounts: {String: UFix64},
+ effects: [String],
+ settings: {CreatureSettings: Int64}
+ ): @Feature {
+ return <-create Feature(name: name, tags: tags, attributes: attributes, defence: defence, potentiality: potentiality, shape: shape, abilities: abilities, items: items, itemAmounts: itemAmounts, effects: effects, settings: settings)
+ }
+
+ // The FeaturesCarrier resource interface is used to get the features of the creature.
+ access(all) resource interface FeaturesCarrier: EntryContainer {
+ access(all) view fun getFeaturesLength(): Int
+ access(all) fun getFeatureIdentifiers(): [EntryIdentifier]
+
+ access(all)
+ fun borrowFeatures(): [&Feature] {
+ return self.getFeatureIdentifiers()
+ .map(view fun (_ x: EntryIdentifier): &Feature? {
+ return x.borrowFeature()
+ })
+ .filter(view fun (_ x: &Feature?): Bool {
+ return x != nil
+ })
+ .map(view fun (_ x: &Feature?): &Feature {
+ return x!
+ })
+ }
+
+ access(all) view
+ fun hasFeatures(): Bool {
+ return self.getFeaturesLength() > 0
+ }
+
+ // ---- Implement Feature Gameplay Methods ----
+
+ access(Host | Editor)
+ fun applyFeature(_ feature: @FungibleEntry) {
+ pre {
+ feature.identifier.verify(LibraryCategory.FEATURE): "Feature identifier is invalid"
+ }
+ }
+ }
+
+ access(all) resource interface UnitCollectionCarrier: UnitCollectionBaseCarrier, FeaturesCarrier {
+ access(all) view fun getFeaturesLength(): Int {
+ let collection = self.borrowReadonlyCollection()
+ return collection.getLengthByCategory(LibraryCategory.FEATURE)
+ }
+
+ access(all)
+ fun getFeatureIdentifiers(): [EntryIdentifier] {
+ let collection = self.borrowReadonlyCollection()
+ return collection.getEntryIdentifiers(LibraryCategory.FEATURE)
+ }
+ }
+
+ access(all) resource interface BioCarrier {
+ access(Host | Editor) view
+ fun borrowWritableBioPrompts(): auth(Mutate) &[String]
+
+ access(all) view fun borrowBioPrompts(): &[String] { return self.borrowWritableBioPrompts() }
+
+ access(Host | Editor)
+ fun addBioPrompt(_ prompt: String) {
+ let prompts = self.borrowWritableBioPrompts()
+ prompts.append(prompt)
+
+ emit CreatureBioPromptAdded(
+ self.owner?.address,
+ uuid: self.uuid,
+ prompt
+ )
+ }
+ }
+
+ /// The EquipableUnit defines the core interface for a game unit, including status, equipment, and inventory logic.
+ access(all) resource interface EquipableCreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, ItemsCarrier, FeaturesCarrier, ShapeCarrier {
+ /// Returns a reference to the creature's own base attributes.
+ ///
+ /// @return Reference to the base Attributes struct.
+ access(all) view fun borrowSelfAttributes(): &Attributes
+
+ /// Returns a reference to the creature's own base potentiality.
+ ///
+ /// @return Reference to the base Potentiality struct.
+ access(all) view fun borrowSelfPotentiality(): &Potentiality? {
+ return nil
+ }
+
+ // ---- Implement ComposableUnitStatusCarrier ----
+
+ /// Collects all attribute sources (self, features, abilities, equipped items) for status calculation.
+ ///
+ /// @return Array of references to all Attributes affecting the creature.
+ access(all)
+ fun borrowAttributesElements(): [&Attributes] {
+ let ret: [&Attributes] = [self.borrowSelfAttributes()]
+
+ // From Features
+ let features = self.borrowFeatures()
+ for feature in features {
+ if feature.hasAttributes() {
+ if let attributes = feature.borrowAttributes() {
+ ret.append(attributes)
+ }
+ }
+ }
+
+ // From Abilities
+ let abilities = self.borrowAbilities()
+ for ability in abilities {
+ if ability.hasAttributes() {
+ if let attributes = ability.borrowAttributes() {
+ ret.append(attributes)
+ }
+ }
+ }
+
+ // From Items
+ let items = self.borrowEquippedItems()
+ for item in items {
+ if item.hasAttributes() {
+ if let attributes = item.borrowAttributes() {
+ ret.append(attributes)
+ }
+ }
+ }
+ return ret
+ }
+
+ /// Collects all defence sources (self, features, equipped items) for status calculation.
+ ///
+ /// @return Array of references to all Defence affecting the creature.
+ access(all)
+ fun borrowDefenceElements(): [&Defence] {
+ let ret: [&Defence] = []
+
+ // From Features
+ let features = self.borrowFeatures()
+ for feature in features {
+ if feature.hasDefence() {
+ if let defence = feature.borrowDefence() {
+ ret.append(defence)
+ }
+ }
+ }
+
+ // From Abilities
+ let abilities = self.borrowAbilities()
+ for ability in abilities {
+ if ability.hasDefence() {
+ if let defence = ability.borrowDefence() {
+ ret.append(defence)
+ }
+ }
+ }
+ // From Items
+ let items = self.borrowEquippedItems()
+ for item in items {
+ if item.hasDefence() {
+ if let defence = item.borrowDefence() {
+ ret.append(defence)
+ }
+ }
+ }
+ return ret
+ }
+
+ /// Collects all potentiality sources (self, features, equipped items) for status calculation.
+ ///
+ /// @return Array of references to all Potentiality affecting the creature.
+ access(all)
+ fun borrowPotentialityElements(): [&Potentiality] {
+ let ret: [&Potentiality] = []
+
+ if let selfPotentiality = self.borrowSelfPotentiality() {
+ ret.append(selfPotentiality)
+ }
+
+ // From Features
+ let features = self.borrowFeatures()
+ for feature in features {
+ if feature.hasPotentiality() {
+ if let potentiality = feature.borrowPotentiality() {
+ ret.append(potentiality)
+ }
+ }
+ }
+
+ // From Items
+ let items = self.borrowEquippedItems()
+ for item in items {
+ if item.hasPotentiality() {
+ if let potentiality = item.borrowPotentiality() {
+ ret.append(potentiality)
+ }
+ }
+ }
+ return ret
+ }
+
+ // ---- Implement Item equipment info ----
+
+ /// Returns all available equipment slots, including those from shape and equipped items.
+ ///
+ /// @return Dictionary of EquipSlot to available count.
+ access(all)
+ fun getSlotsAll(): {EquipSlot: UInt8} {
+ let all: {EquipSlot: UInt8} = {}
+
+ // All Slots = Shape Slots + Equipped Items Slots
+ let shape = self.borrowShape() ?? panic("Shape not found")
+ let shapeSlots = shape.slotsAvailable
+
+ // Shape Slots
+ for slot in shapeSlots.keys {
+ all[slot] = shapeSlots[slot]!
+ }
+
+ // Equipped Items Slots
+ let equippedItems = self.borrowEquippedItems()
+ for item in equippedItems {
+ if item.isProvidedSlots() {
+ let slots = item.slotsProvided
+ for slot in slots.keys {
+ if all[slot] == nil {
+ all[slot] = slots[slot]!
+ } else {
+ all[slot] = all[slot]! + slots[slot]!
+ }
+ }
+ }
+ }
+
+ return all
+ }
+
+ /// Returns a mutable reference to the slots occupied by equipped items.
+ ///
+ /// @return Mutable reference to the EquipSlot mapping.
+ access(Host | Editor) view
+ fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} {
+ let status = self.borrowStatus()
+ return status.borrowWritableSlotsOccupied()
+ }
+
+ /// Checks if a specific item is currently equipped.
+ ///
+ /// @param item The EntryIdentifier of the item to check.
+ /// @return True if the item is equipped, false otherwise.
+ access(all) view
+ fun hasItemEquipped(_ item: EntryIdentifier): Bool {
+ let slots = self.borrowSlotsOccupied()
+ let id = item.getStringID()
+ for slot in slots.keys {
+ if let occupied = slots[slot] {
+ if occupied.contains(id) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ /// Returns all currently equipped items as references.
+ ///
+ /// @return Array of references to equipped Item resources.
+ access(all)
+ fun borrowEquippedItems(): [&Item] {
+ let ret: [&Item] = []
+
+ let slots = self.borrowSlotsOccupied()
+ // get unique ids
+ let uniqueIds: [String] = []
+ for slot in slots.keys {
+ if let occupied = slots[slot] {
+ for id in occupied {
+ if !uniqueIds.contains(id) {
+ uniqueIds.append(id)
+ }
+ }
+ }
+ }
+ // borrow the items
+ for id in uniqueIds {
+ if let entry = self.borrowEntryByID(id) {
+ if let item = entry.identifier.borrowItem() {
+ ret.append(item)
+ }
+ }
+ }
+ return ret
+ }
+
+ /// Checks if an item can be equipped by the creature.
+ ///
+ /// @param item The EntryIdentifier of the item to check.
+ /// @return True if the item can be equipped, false otherwise.
+ access(all)
+ fun isItemEquippable(_ item: EntryIdentifier): Bool {
+ if let itemRef = item.borrowItem() {
+ return self._isItemEquippable(itemRef)
+ }
+ return false
+ }
+
+ /// Equips an item to the creature, updating occupied slots and emitting an event.
+ ///
+ /// @param item The EntryIdentifier of the item to equip.
+ access(Editor | Host | Player)
+ fun equipItem(_ item: EntryIdentifier) {
+ let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID()))
+ let itemId = item.getStringID()
+ assert(self.borrowEntryByID(itemId) != nil, message: "Not Found in Inventory, Item: ".concat(itemId))
+ assert(!self.hasItemEquipped(item), message: "Already Equipped, Item: ".concat(itemId))
+ assert(self._isItemEquippable(itemRef), message: "Not Equippable, Item: ".concat(itemId))
+
+ let slots = self.borrowSlotsOccupied()
+ let requiredSlots = itemRef.slotsOccupied
+ for slot in requiredSlots.keys {
+ if let occupied = slots[slot] {
+ let newOccupied: [String] = *occupied
+ var toOccupy = requiredSlots[slot]!
+ while toOccupy > 0 {
+ newOccupied.append(itemId)
+ toOccupy = toOccupy - 1
+ }
+ slots[slot] = newOccupied
+ }
+ }
+
+ emit CreatureItemEquipped(
+ item.library,
+ itemId,
+ self.owner?.address,
+ itemUUID: itemRef.uuid,
+ uuid: self.uuid
+ )
+ }
+
+ /// Unequips an item from the creature, updating occupied slots and emitting an event.
+ ///
+ /// @param item The EntryIdentifier of the item to unequip.
+ access(Editor | Host | Player)
+ fun unequipItem(_ item: EntryIdentifier) {
+ let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID()))
+ let itemId = item.getStringID()
+ assert(self.borrowEntryByID(itemId) != nil, message: "Not Found in Inventory, Item: ".concat(itemId))
+ assert(self.hasItemEquipped(item), message: "Not Equipped, Item: ".concat(itemId))
+
+ let slots = self.borrowSlotsOccupied()
+ let requiredSlots = itemRef.slotsOccupied
+ for slot in requiredSlots.keys {
+ if let occupied = slots[slot] {
+ let newOccupied: [String] = []
+ for id in occupied {
+ if id != itemId {
+ newOccupied.append(id)
+ }
+ }
+ slots[slot] = newOccupied
+ }
+ }
+
+ emit CreatureItemUnequipped(
+ item.library,
+ itemId,
+ self.owner?.address,
+ itemUUID: itemRef.uuid,
+ uuid: self.uuid
+ )
+ }
+
+ /// Internal: Checks if an item reference can be equipped based on slot availability.
+ ///
+ /// @param itemRef Reference to the Item resource.
+ /// @return True if the item can be equipped, false otherwise.
+ access(contract)
+ fun _isItemEquippable(_ itemRef: &Item): Bool {
+ let allSlots = self.getSlotsAll()
+ let occupiedSlots = self.borrowSlotsOccupied()
+ let requiredSlots = itemRef.slotsOccupied;
+ for slot in requiredSlots.keys {
+ let allCount = allSlots[slot] ?? 0
+ let occupiedCount = UInt8(occupiedSlots[slot]?.length ?? 0)
+ if allCount - occupiedCount < requiredSlots[slot]! {
+ return false
+ }
+ }
+ return true
+ }
+
+ // --- Equipable Methods - Feature, Read ---
+
+ // Returns a map of fixed abilities (from features) by their string ID
+ access(all)
+ fun getFixedAbilities(): {String: Bool} {
+ let ret: {String: Bool} = {}
+ let features = self.borrowFeatures()
+
+ for feature in features {
+ if feature.hasAbilities() {
+ let abilities = feature.getAbilityIdentifiers()
+ for ability in abilities {
+ if ret[ability.getStringID()] == nil {
+ ret[ability.getStringID()] = true
+ }
+ }
+ }
+ }
+ return ret
+ }
+
+ // Returns a map of fixed items (from features) and their amounts
+ access(all)
+ fun getFixedItems(): {String: UFix64} {
+ let ret: {String: UFix64} = {}
+ let features = self.borrowFeatures()
+
+ for feature in features {
+ if feature.hasItems() {
+ let items = feature.borrowItemEntries()
+ for item in items {
+ let itemId = item.identifier.getStringID()
+ ret[itemId] = item.balance + (ret[itemId] ?? 0.0)
+ }
+ }
+ }
+ return ret
+ }
+ }
+
+ // This is a resource that can apply features to itself
+ access(all) resource interface StaticCollectionWithFeatures: StaticCollectionUnit {
+ access(all)
+ fun applyFeature(_ feature: @FungibleEntry) {
+ self.deposit(entry: <- feature)
+ }
+ }
+
+ // This is a resource that can store bio prompts
+ access(all) resource interface BioPromptsUnit {
+ access(all) let bioPrompts: [String]
+
+ access(Host) view
+ fun borrowWritableBioPrompts(): auth(Mutate) &[String] {
+ return &self.bioPrompts
+ }
+ }
+
+ // This is a resource that can store a merged status
+ access(all) resource interface MergableStatusUnit {
+ access(all) let status: UnitStatus
+
+ /// Returns a reference to the merged status of the creature.
+ ///
+ /// @return Reference to the UnitStatus struct.
+ access(all) view
+ fun borrowStatus(): &UnitStatus {
+ return &self.status
+ }
+ }
+
+ /// The Creature resource represents a static, character template with fixed attributes, defence, potentiality, and inventory.
+ access(all) resource Creature: Nameable, BioPromptsUnit, MergableStatusUnit, EquipableCreatureInterface, UnitCollectionCarrier, SettingsUnit, StaticCollectionWithFeatures {
+ /// The name of the creature.
+ access(all) let name: String
+ /// The tags associated with the creature.
+ access(all) let tags: [String]
+ /// The collection of entries (items, abilities, etc.) owned by the creature.
+ access(all) let collection: @EntryCollection
+ /// The settings of the creature (e.g., gender, size, etc.).
+ access(all) let settings: {CreatureSettings: Int64}
+ /// The base attributes of the creature.
+ access(all) let baseAttributes: Attributes
+ /// The merged status of the creature (after applying features, items, etc.).
+ access(all) let status: UnitStatus
+ /// The bio prompts of the creature.
+ access(all) let bioPrompts: [String]
+
+ /// Initializes a new Creature resource.
+ ///
+ /// @param name The name of the creature.
+ /// @param tags The tags associated with the creature.
+ /// @param shape The EntryIdentifier for the creature's shape.
+ /// @param settings The settings for the creature.
+ /// @param attributes The base attributes.
+ /// @param defence The base defence.
+ /// @param potentiality The base potentiality.
+ /// @param features The EntryIdentifiers for features to apply.
+ /// @param abilities The EntryIdentifiers for abilities to add.
+ /// @param items The EntryIdentifiers for items to add.
+ /// @param itemAmounts The mapping of item IDs to their amounts.
+ /// @param bioPrompts The bio prompts for the creature.
+ init(
+ name: String,
+ tags: [String],
+ shape: EntryIdentifier,
+ settings: {CreatureSettings: Int64},
+ attributes: Attributes,
+ features: [EntryIdentifier],
+ abilities: [EntryIdentifier],
+ items: [EntryIdentifier],
+ itemAmounts: {String: UFix64},
+ bioPrompts: [String]
+ ) {
+ self.name = name
+ self.tags = tags
+ self.settings = settings
+ self.bioPrompts = bioPrompts
+ self.baseAttributes = attributes
+ // Initialize the status
+ self.status = UnitStatus(
+ attributes: self.baseAttributes,
+ defence: Defence(physical: 0,endurance: 0,resistance: 0),
+ potentiality: Potentiality(initial: 0)
+ )
+ self.collection <- create EntryCollection()
+
+ // Set the entries for shape, abilities, items
+ self.applyShape(<-create FungibleEntry(identifier: shape, amount: 1.0))
+
+ for featureIdentifier in features {
+ self.applyFeature(<-create FungibleEntry(identifier: featureIdentifier, amount: 1.0))
+ }
+
+ for abilityIdentifier in abilities {
+ self.gainAbility(<-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0))
+ }
+
+ if items.length > 0 {
+ assert(itemAmounts.length == items.length, message: "Item amounts must be the same length as items")
+ for itemIdentifier in items {
+ assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid")
+ let id = itemIdentifier.getStringID()
+ let amount = itemAmounts[id] ?? 0.0
+ self.lootItem(<-create FungibleEntry(identifier: itemIdentifier, amount: amount))
+ }
+ }
+
+ // apply the status
+ self.applyStatus(true)
+ }
+
+ /// Returns a reference to the base attributes of the creature.
+ ///
+ /// @return Reference to the base Attributes struct.
+ access(all) view
+ fun borrowSelfAttributes(): &Attributes {
+ return &self.baseAttributes
+ }
+ }
+
+ // Method to create a creature template
+ access(all)
+ fun createCreatureTemplate(
+ name: String,
+ tags: [String],
+ shape: EntryIdentifier,
+ settings: {CreatureSettings: Int64},
+ attributes: Attributes,
+ features: [EntryIdentifier],
+ abilities: [EntryIdentifier],
+ items: [EntryIdentifier],
+ itemAmounts: {String: UFix64},
+ bioPrompts: [String]
+ ): @Creature {
+ return <-create Creature(name: name, tags: tags, shape: shape, settings: settings, attributes: attributes, features: features, abilities: abilities, items: items, itemAmounts: itemAmounts, bioPrompts: bioPrompts)
+ }
+
+ // ------------ Playable Unit ------------
+
+ access(all) attachment Health for ComposableUnitStatusCarrier {
+ access(all) let info: Attributes
+
+ view init() {
+ let status = base.borrowStatus()
+ self.info = Attributes(
+ strength: status.attributes.strength,
+ vitality: status.attributes.vitality,
+ spirit: status.attributes.spirit
+ )
+ }
+
+ // ---- Gameplay Methods, Read ---
+
+ // Returns true if any of the health attributes are zero or below, indicating the unit is stunned
+ access(all) view
+ fun isStunned(): Bool {
+ return self.info.strength <= 0 || self.info.vitality <= 0 || self.info.spirit <= 0
+ }
+
+ // Returns true if all health attributes are zero or below, indicating the unit is dead
+ access(all) view
+ fun isDead(): Bool {
+ return self.info.strength <= 0 && self.info.vitality <= 0 && self.info.spirit <= 0
+ }
+
+ // ---- Gameplay Methods, Write ---
+
+ // Resets the unit's health to match its current status attributes
+ access(Host)
+ fun resetHealth() {
+ let status = base.borrowStatus()
+
+ self.info.setValue(AttributeType.STRENGTH, status.attributes.strength)
+ self.info.setValue(AttributeType.VITALITY, status.attributes.vitality)
+ self.info.setValue(AttributeType.SPIRIT, status.attributes.spirit)
+
+ emit PawnHealthReset(
+ base.owner?.address ?? panic("Owner not found"),
+ self.info.strength,
+ self.info.vitality,
+ self.info.spirit,
+ uuid: base.uuid
+ )
+ }
+
+ // Recovers a specific attribute of health by a given amount, not exceeding the max value
+ access(Host)
+ fun recoverHealth(_ type: AttributeType, _ amount: Int64) {
+ let status = base.borrowStatus()
+
+ let maxAttr = status.attributes.getValue(type)
+ let currentAttr = self.info.getValue(type)
+
+ var recoverAmount = amount
+ if currentAttr + amount > maxAttr {
+ recoverAmount = maxAttr - currentAttr
+ }
+
+ self.info.setValue(type, currentAttr + recoverAmount)
+
+ emit PawnHealthRecovered(
+ base.owner?.address ?? panic("Owner not found"),
+ type.rawValue,
+ recoverAmount,
+ uuid: base.uuid
+ )
+ }
+
+ // Applies damage to a specific health attribute based on attack and defense values
+ access(Host)
+ fun damageHealth(
+ _ attacks: {AttackType: Int64},
+ _ penetration: Int64,
+ _ type: AttributeType,
+ _ extraDefence: Defence?
+ ) {
+ let status = base.borrowStatus()
+
+ var biggestDamage: Int64 = 0
+ for attackType in attacks.keys {
+ let atk = attacks[attackType] ?? 0
+ var def = status.defence.getDefenceFrom(attackType)
+ if extraDefence != nil {
+ def = def + extraDefence!.getDefenceFrom(attackType)
+ }
+ if def > penetration {
+ def = def - penetration
+ } else {
+ def = 0
+ }
+
+ let damage = atk - def
+ if damage > biggestDamage {
+ biggestDamage = damage
+ }
+ }
+
+ self.info.addValue(type, -1 * biggestDamage)
+
+ emit PawnHealthDamaged(
+ base.owner?.address ?? panic("Owner not found"),
+ type.rawValue,
+ biggestDamage,
+ uuid: base.uuid
+ )
+ }
+ }
+
+ // PlayableUnit interface represents a unit that can participate in gameplay and have health-related actions
+ access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier {
+ // Returns a reference to the unit's health
+ access(all) view
+ fun borrowHealth(): &Health? {
+ return self.borrowWritableHealth()
+ }
+
+ // Returns a mutable reference to the unit's health
+ access(Host) view
+ fun borrowWritableHealth(): auth(Host) &Health? {
+ let selfRef = &self as auth(Host) &{ComposableUnitStatusCarrier}
+ return selfRef[Health]
+ }
+ }
+
+ access(all) resource interface EquipableUnit: EquipableCreatureInterface {
+ // ---- Implement Feature Gameplay Methods ----
+
+ // This is static, so we don't need to apply it for now
+ access(Host | Editor)
+ fun applyFeature(_ feature: @FungibleEntry) {
+ // borrow the feature
+ let featureRef = feature.identifier.borrowFeature() ?? panic("Not Exists, Feature: ".concat(feature.identifier.getStringID()))
+
+ // apply the feature to the collection
+ self.deposit(entry: <- feature)
+
+ // generate the abilities and items from the feature
+ let abilitiesFromFeature = featureRef.getAbilityIdentifiers()
+ for abilityIdentifier in abilitiesFromFeature {
+ self.gainAbility(<-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0))
+ }
+
+ // generate the items from the feature
+ let itemsFromFeature = featureRef.borrowItemEntries()
+ for itemEntry in itemsFromFeature {
+ self.lootItem(<- create FungibleEntry(identifier: itemEntry.identifier.clone(), amount: itemEntry.balance))
+ }
+ }
+
+ // ---- Equipable Item Gameplay Methods ----
+
+ // Adds an item to the unit's collection and equips it if possible
+ access(Host | Editor)
+ fun lootItem(_ entry: @FungibleEntry) {
+ let itemId = entry.identifier
+ let itemRef = itemId.borrowItem() ?? panic("Not Exists, Item: ".concat(itemId.getStringID()))
+
+ self.deposit(entry: <-entry)
+
+ if itemRef.isEquippable() && self._isItemEquippable(itemRef) {
+ self.equipItem(itemId)
+ }
+
+ self.applyStatus(false)
+ }
+
+ // Removes an item from the unit's collection, unequipping it if necessary
+ access(Host | Editor)
+ fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry {
+ if self.hasItemEquipped(item) {
+ self.unequipItem(item)
+ }
+
+ let ret <- self.withdraw(item.getStringID(), amount: amount)
+
+ self.applyStatus(false)
+
+ return <- ret
+ }
+ }
+
+ // CultivableUnit interface represents a unit that can be cultivated (upgraded) by using potentiality
+ access(all) resource interface CultivableUnit: EquipableUnit, UnitCollectionCarrier {
+ // The potentiality used by the character
+ access(all) var potentialityUsed: UInt64
+ // The potentiality obtained by the character
+ access(all) var potentialityObtained: UInt64
+ // The cultivation progress for each ability (by string ID)
+ access(all) var cultivation: {String: UInt64}
+
+ // Returns a mutable reference to the unit's cultivable attributes
+ access(Host) view fun borrowCultivableAttributes(): &Attributes
+
+ // ---- Cultivable Methods, Read ----
+
+ // Returns the amount of potentiality available for upgrades
+ access(all) view
+ fun getUsablePotentiality(): UInt64 {
+ let status = self.borrowStatus()
+ return UInt64(status.potentiality.initial) + self.potentialityObtained - self.potentialityUsed
+ }
+
+ // Checks if the unit can upgrade a specific attribute based on available potentiality
+ access(all) view
+ fun canUpgradeAttribute(_ type: AttributeType): Bool {
+ // +1 attribute requires unused potentiality = 3 x current attribute value
+ let attributes = self.borrowCultivableAttributes()
+ let currentValue = attributes.getValue(type)
+ let unusedPotentiality = self.getUsablePotentiality()
+ return Int64(unusedPotentiality) >= 3 * currentValue
+ }
+
+ // --- Cultivable Methods - Attribute, Write ---
+
+ // Increases the amount of potentiality obtained
+ access(Host)
+ fun gainPotentiality(_ amount: UInt64) {
+ self.potentialityObtained = self.potentialityObtained + amount
+
+ emit PawnPotentialityGained(
+ self.owner?.address ?? panic("Owner not found"),
+ amount,
+ uuid: self.uuid
+ )
+ }
+
+ // Upgrades a specific attribute by consuming potentiality
+ access(Host | Player)
+ fun upgradeAttribute(_ type: AttributeType, _ amount: UInt64) {
+ let attributes = self.borrowCultivableAttributes()
+
+ var toUpgrade = amount
+ while toUpgrade > 0 {
+ assert(self.canUpgradeAttribute(type), message: "Not enough potentiality to upgrade attribute")
+ // consume potentiality = 3 x current attribute value
+ let currentValue = attributes.getValue(type)
+ // consume potentiality
+ self.consumePotentiality(UInt64(currentValue * 3))
+
+ // upgrade attribute
+ attributes.addValue(type, 1)
+
+ toUpgrade = toUpgrade - 1
+ }
+
+ emit PawnAttributeUpgraded(
+ self.owner?.address ?? panic("Owner not found"),
+ type.rawValue,
+ amount,
+ uuid: self.uuid
+ )
+ }
+
+ // --- Cultivable Methods - Ability, Read ---
+
+ // Returns the cultivation level for a specific ability
+ access(all) view
+ fun getCultivationLevel(_ ability: EntryIdentifier): UInt64 {
+ return self.cultivation[ability.getStringID()] ?? 0
+ }
+
+ // Returns the total attributes occupied by all countable abilities
+ access(all)
+ fun getAbilitiesOccupiedAttributes(): Attributes {
+ let occupied = Attributes(strength: 0, vitality: 0, spirit: 0)
+ let ownedAbilities = self.getAbilityIdentifiers()
+ let fixedAbilities = self.getFixedAbilities()
+
+ // fixed abilities are not counted
+ let countableAbilities = ownedAbilities.filter(view fun (ability: EntryIdentifier): Bool {
+ return fixedAbilities[ability.getStringID()] == nil
+ })
+
+ for abilityRef in countableAbilities {
+ let abilityRef = abilityRef.borrowAbility() ?? panic("Not Exists, Ability: ".concat(abilityRef.getStringID()))
+ if let occupy = abilityRef.occupy {
+ occupied.addValue(occupy, Int64(abilityRef.level))
+ }
+ }
+ return occupied
+ }
+
+ // --- Cultivable Methods - Ability, Write ---
+
+ // Increases the cultivation level of an ability by consuming potentiality
+ access(Host | Player)
+ fun cultivateAbility(_ ability: EntryIdentifier, consume: UInt64, abilityUp: UInt64) {
+ let itemId = ability.getStringID()
+ assert(self.borrowEntryByID(itemId) != nil, message: "Ability already exists")
+
+ self.consumePotentiality(consume)
+
+ let cultivation = self.cultivation[ability.getStringID()] ?? 0
+ self.cultivation[ability.getStringID()] = cultivation + abilityUp
+
+ emit PawnAbilityCultivated(
+ self.owner?.address ?? panic("Owner not found"),
+ ability.getStringID(),
+ consume,
+ abilityUp,
+ uuid: self.uuid
+ )
+ }
+
+ // Adds a new ability to the unit, consuming potentiality and updating status
+ access(Host | Editor)
+ fun gainAbility(_ ability: @FungibleEntry) {
+ let itemId = ability.identifier.getStringID()
+ assert(self.borrowEntryByID(itemId) == nil, message: "Ability already exists")
+
+ self.cultivation[itemId] = 0
+
+ let abilityRef = ability.identifier.borrowAbility() ?? panic("Not Exists, Ability: ".concat(itemId))
+
+ // check if the ability is fixed
+ let fixedAbilities = self.getFixedAbilities()
+ // For fixed abilities, we don't consume potentiality
+ if fixedAbilities[itemId] != true {
+ // Consume potentiality = 3 x ability level
+ self.consumePotentiality(abilityRef.level * 3)
+
+ let status = self.borrowStatus()
+ // Check ability occupied value
+ if let attributeToOccupy = abilityRef.occupy {
+ let occupied = self.getAbilitiesOccupiedAttributes()
+ let currentAttr = status.attributes.getValue(attributeToOccupy)
+ let occupiedAttr = occupied.getValue(attributeToOccupy)
+ assert(occupiedAttr + Int64(abilityRef.level) <= currentAttr, message: "Attribute is full")
+ }
+ }
+
+ // Add ability to collection
+ self.deposit(entry: <- ability)
+
+ // Apply the ability effects
+ self.applyStatus(false)
+ }
+
+ // Removes an ability from the unit and resets its cultivation
+ access(Host | Editor)
+ fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry {
+ let itemId = ability.getStringID()
+ assert(self.borrowEntryByID(itemId) != nil, message: "Ability not exists")
+
+ // Clear cultivation
+ self.cultivation[itemId] = 0
+
+ let ret <- self.withdraw(itemId, amount: nil)
+
+ self.applyStatus(false)
+
+ return <- ret
+ }
+
+ // --- Cultivable Methods - Potentiality, Write ---
+
+ // Consumes a specified amount of potentiality for upgrades or actions
+ access(contract)
+ fun consumePotentiality(_ consume: UInt64) {
+ pre {
+ self.getUsablePotentiality() >= consume: "Not enough potentiality to consume, consume: "
+ .concat(consume.toString())
+ .concat(", usable: ")
+ .concat(self.getUsablePotentiality().toString())
+ }
+ self.potentialityUsed = self.potentialityUsed + consume
+
+ emit PawnPotentialityConsumed(
+ self.owner?.address ?? panic("Owner not found"),
+ consume,
+ self.getUsablePotentiality(),
+ self.potentialityUsed,
+ uuid: self.uuid
+ )
+ }
+ }
+
+ // The PointXY struct is used to store the position of a point in the game board
+ access(all) struct PointXY {
+ // The x coordinate of the point
+ access(all) var x: Int32
+ // The y coordinate of the point
+ access(all) var y: Int32
+
+ init(_ x: Int32, _ y: Int32) {
+ self.x = x
+ self.y = y
+ }
+
+ access(all)
+ fun set(_ x: Int32, _ y: Int32) {
+ self.x = x
+ self.y = y
+ }
+
+ access(all)
+ fun setX(_ x: Int32) {
+ self.x = x
+ }
+
+ access(all)
+ fun setY(_ y: Int32) {
+ self.y = y
+ }
+
+ access(all)
+ fun addX(_ x: Int32) {
+ self.x = self.x + x
+ }
+
+ access(all)
+ fun addY(_ y: Int32) {
+ self.y = self.y + y
+ }
+ }
+
+ // The Position attachment for ComposableUnitStatusCarrier, it is used to store the position of the unit for game board
+ // Only when the unit is stored in the board, the attachment will be initialized
+ access(all)
+ attachment PositionXY for ComposableUnitStatusCarrier {
+ // Board Information
+ access(all) let board: BoardIdentifier
+ // The position of the unit
+ access(all) let point: PointXY
+
+ init(_ board: Address, _ boardUuid: UInt64) {
+ self.board = BoardIdentifier(board, boardUuid)
+
+ self.point = PointXY(0, 0)
+ }
+
+ // Sets the position of the unit
+ access(Host)
+ fun setPosition(_ x: Int32, _ y: Int32) {
+ self.point.set(x, y)
+
+ emit PawnPositionUpdated(
+ self.board.container,
+ self.board.id,
+ unit: base.uuid,
+ x: self.point.x,
+ y: self.point.y,
+ )
+ }
+
+ // Returns the x coordinate of the unit
+ access(all) view
+ fun getX(): Int32 {
+ return self.point.x
+ }
+
+ // Returns the y coordinate of the unit
+ access(all) view
+ fun getY(): Int32 {
+ return self.point.y
+ }
+ }
+
+ // The PositionableUnit interface represents a unit that can be positioned on the game board
+ access(all) resource interface PositionableUnit: ComposableUnitStatusCarrier {
+ // Returns whether the unit is on the board
+ access(all) view
+ fun isOnBoard(): Bool {
+ return self.borrowPosition() != nil
+ }
+
+ // The position of the unit
+ access(all) view
+ fun borrowPosition(): &PositionXY? {
+ return self.borrowWritablePosition()
+ }
+
+ // Returns a mutable reference to the position of the unit
+ access(Host) view
+ fun borrowWritablePosition(): auth(Host) &PositionXY? {
+ let selfRef = &self as auth(Host) &{ComposableUnitStatusCarrier}
+ return selfRef[PositionXY]
+ }
+ }
+
+ // Pawn resource represents a playable and cultivable character in the game
+ access(all) resource Pawn: PlayableUnit, PositionableUnit, CultivableUnit, CollectionContainerUnit, BioPromptsUnit, MergableStatusUnit, SettingsUnit {
+ // The address of the library this pawn belongs to
+ access(contract) let library: Address
+ access(contract) let template: EntryIdentifier?
+
+ // Initial potentiality (will not be changed after initialization)
+ access(self) let initPotentiality: Potentiality
+
+ // --- Status Properties ---
+
+ // The merged status of the character
+ access(all) let status: UnitStatus
+
+ // --- Cultivable Property ---
+
+ // The potentiality used by the character
+ access(all) var potentialityUsed: UInt64
+ // The potentiality obtained by the character
+ access(all) var potentialityObtained: UInt64
+ // The cultivation of the character
+ access(all) var cultivation: {String: UInt64}
+
+ // Cultivable attributes, can be upgraded by potentiality
+ access(all) let attributes: Attributes
+
+ // The collection of the character (items, abilities, etc.)
+ access(all) let collection: @EntryCollection
+
+ // --- Settings ---
+
+ // The settings of the character
+ access(all) let settings: {CreatureSettings: Int64}
+ // The bio prompts of the character
+ access(all) let bioPrompts: [String]
+
+ // Initializes a new Pawn with the given parameters
+ init(
+ _ library: Address,
+ template: EntryIdentifier?,
+ shape: EntryIdentifier?,
+ features: [EntryIdentifier],
+ items: [EntryIdentifier],
+ itemAmounts: {String: UFix64},
+ abilities: [EntryIdentifier],
+ bioPrompts: [String]
+ ) {
+ self.library = library
+
+ self.settings = {}
+ self.bioPrompts = bioPrompts
+
+ // Clone the parameters
+ var shapeToApply = shape
+
+ let featuresToApply = features
+ let featuresDict: {String: Bool} = {}
+ for feature in features {
+ featuresDict[feature.getStringID()] = true
+ }
+
+ let itemsToApply = items
+ let itemAmountsToApply = itemAmounts
+ for item in items {
+ let itemId = item.getStringID()
+ itemAmountsToApply[itemId] = itemAmounts[itemId] ?? 0.0
+ }
+
+ let abilitiesToApply = abilities
+ let abilitiesDict: {String: Bool} = {}
+ for ability in abilities {
+ abilitiesDict[ability.getStringID()] = true
+ }
+
+ // set the template
+ self.template = template
+
+ // Apply the template if it exists
+ if template != nil {
+ let creature = template!.borrowCreature() ?? panic("Not Exists, Creature: ".concat(template!.getStringID()))
+
+ // Extract template's Abilities, Potentiality
+ self.attributes = creature.borrowSelfAttributes().copy() as! Attributes
+ self.initPotentiality = Potentiality(initial: 0)
+
+ // If the shape is not provided, use the template's shape
+ if shapeToApply == nil {
+ // A creature must have a shape
+ let templateShapeIdentifier = creature.getShapeIdentifier() ?? panic("Shape not exists for Creature: ".concat(template!.getStringID()))
+ shapeToApply = templateShapeIdentifier
+ }
+
+ // Extract template's Features
+ let templateFeatures = creature.getFeatureIdentifiers()
+ for feature in templateFeatures {
+ let featureId = feature.getStringID()
+ if featuresDict[featureId] == nil {
+ featuresToApply.append(feature)
+ featuresDict[featureId] = true
+ }
+ }
+
+ // Extract template's Abilities
+ let templateAbilities = creature.getAbilityIdentifiers()
+ for ability in templateAbilities {
+ let abilityId = ability.getStringID()
+ if abilitiesDict[abilityId] == nil {
+ abilitiesToApply.append(ability)
+ }
+ }
+
+ // Extract template's Items
+ let templateItems = creature.borrowItemEntries()
+ for item in templateItems {
+ let itemId = item.identifier.getStringID()
+ itemAmountsToApply[itemId] = (itemAmountsToApply[itemId] ?? 0.0) + item.balance
+ }
+
+ // Apply the template's settings
+ if creature.hasSettings() {
+ let templateSettings = creature.borrowWritableSettings()
+ for key in templateSettings.keys {
+ self.settings[key] = templateSettings[key]!
+ }
+ }
+
+ // Apply the template's bio prompts
+ let templateBioPrompts = creature.borrowWritableBioPrompts()
+ for prompt in templateBioPrompts {
+ self.bioPrompts.append(prompt)
+ }
+ } else {
+ // If the template is not provided, that is a player, so we need to use the library's settings
+
+ let lib = FGameMishal.borrowLibrary(library) ?? panic("Library not found")
+ let initPtt = lib.settings[LibrarySettings.DEFAULT_POTENTIALITY] ?? 36
+ let initAttr = lib.settings[LibrarySettings.DEFAULT_ATTRIBUTE_VALUE] ?? 3
+
+ self.initPotentiality = Potentiality(initial: initPtt)
+ self.attributes = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr)
+ }
+
+ // Initialize the status and health, but it will be reset later
+ self.status = UnitStatus(
+ attributes: self.attributes,
+ defence: Defence(physical: 0,endurance: 0,resistance: 0),
+ potentiality: self.initPotentiality
+ )
+
+ // Initialize the cultivable properties
+ self.potentialityUsed = 0
+ self.potentialityObtained = 0
+ self.cultivation = {}
+
+ // Initialize the collection
+ self.collection <- create EntryCollection()
+
+ // Set the entries for shape, abilities, items
+ self.applyShape(<-create FungibleEntry(identifier: shapeToApply!, amount: 1.0))
+
+ for featureIdentifier in featuresToApply {
+ self.applyFeature(<-create FungibleEntry(identifier: featureIdentifier, amount: 1.0))
+ }
+
+ for abilityIdentifier in abilitiesToApply {
+ self.gainAbility(<-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0))
+ }
+
+ if itemsToApply.length > 0 {
+ assert(itemAmountsToApply.length == itemsToApply.length, message: "Item amounts must be the same length as items")
+ for itemIdentifier in itemsToApply {
+ let id = itemIdentifier.getStringID()
+ let amount = itemAmountsToApply[id] ?? 0.0
+ self.lootItem(<-create FungibleEntry(identifier: itemIdentifier, amount: amount))
+ }
+ }
+
+ // Apply the status and reset the health
+ self.applyStatus(true)
+ }
+
+ // ---- Interface Implementation ----
+
+ // Returns a reference to the pawn's cultivable attributes
+ access(all) view
+ fun borrowSelfAttributes(): &Attributes {
+ return self.borrowCultivableAttributes()
+ }
+
+ // Returns a reference to the pawn's initial potentiality
+ access(all) view
+ fun borrowSelfPotentiality(): &Potentiality {
+ return &self.initPotentiality
+ }
+
+ // Returns a mutable reference to the pawn's cultivable attributes
+ access(Host) view
+ fun borrowCultivableAttributes(): auth(Mutate) &Attributes {
+ return &self.attributes
+ }
+ }
+
+ // ------------ Host and Player Resources ------------
+
+ access(all) resource interface PawnSpawner {
+ // Returns whether the player is spawnable
+ access(Host) view
+ fun isPlayerSpawnable(_ player: Address): Bool
+
+ // Creates a new pawn
+ access(Host)
+ fun createPawn(
+ _ library: Address,
+ template: EntryIdentifier?,
+ shape: EntryIdentifier?,
+ features: [EntryIdentifier],
+ items: [EntryIdentifier],
+ itemAmounts: {String: UFix64},
+ abilities: [EntryIdentifier],
+ bioPrompts: [String]
+ ): @Pawn {
+ return <- create Pawn(
+ library,
+ template: template,
+ shape: shape,
+ features: features,
+ items: items,
+ itemAmounts: itemAmounts,
+ abilities: abilities,
+ bioPrompts: bioPrompts
+ )
+ }
+
+ // Stores a pawn in the player's pocket
+ access(Host)
+ fun storePawn(_ player: Address, _ pawn: @Pawn) {
+ pre {
+ self.isPlayerSpawnable(player): "Player is not spawnable"
+ }
+ let pocketRef = FGameMishal.borrowPlayerPocket(player)
+ ?? panic("Player pocket not found")
+ pocketRef.addPawn(<- pawn)
+ }
+ }
+
+ // The PlayerPocket resource is used to store the pawns of the player
+ // This resource is stored in the player's account
+ access(all) resource PlayerPocket {
+ // UserId => Pawn
+ access(contract)
+ let owned: @{UInt64: Pawn}
+
+ view init() {
+ self.owned <- {}
+ }
+
+ // Returns the number of pawns owned by the player
+ access(all) view
+ fun getOwnedLength(): Int {
+ return self.owned.length
+ }
+
+ // Returns the IDs of the pawns owned by the player
+ access(all) view
+ fun getOwnedIDs(): [UInt64] {
+ return self.owned.keys
+ }
+
+ access(all) view
+ fun isPawnOnBoard(_ id: UInt64): Bool {
+ let pawnRef = self.borrowFullWritablePawn(id)
+ ?? panic("Pawn not found")
+ return pawnRef.isOnBoard()
+ }
+
+ // Returns a reference to the pawn with the given ID
+ access(all) view
+ fun borrowPawn(_ id: UInt64): &Pawn? {
+ return self.borrowFullWritablePawn(id)
+ }
+
+ access(Player) view
+ fun borrowEditablePawn(_ id: UInt64): auth(Player) &Pawn? {
+ return self.borrowFullWritablePawn(id)
+ }
+
+ // Deploys a pawn to the player's pocket
+ access(contract)
+ fun addPawn(_ pawn: @Pawn) {
+ let library = pawn.library
+ let template = pawn.template?.getStringID()
+ let uuid = pawn.uuid
+ self.owned[pawn.uuid] <-! pawn
+
+ emit PawnAddedToPocket(
+ library,
+ self.owner?.address,
+ template,
+ uuid,
+ )
+ }
+
+ // Attaches a pawn to the board
+ access(contract)
+ fun attachToBoard(_ id: UInt64, _ board: Address, _ boardUuid: UInt64) {
+ let pawnRef = self.borrowFullWritablePawn(id)
+ ?? panic("Pawn not found")
+ assert(!pawnRef.isOnBoard(), message: "Pawn should not be on the board")
+ let library = pawnRef.library
+ let uuid = pawnRef.uuid
+
+ let pawn <- self.owned.remove(key: id) ?? panic("Pawn not found")
+ // Attach position to the pawn
+ let pawnWithPos <- attach PositionXY(board, boardUuid) to <- pawn
+ // Attach health to the pawn
+ let pawnWithHealth <- attach Health() to <- pawnWithPos
+
+ // Store the pawn with the position and health
+ self.owned[uuid] <-! pawnWithHealth
+
+ emit PawnAttachedToBoard(
+ library,
+ self.owner?.address ?? panic("Player not exists"),
+ uuid,
+ board,
+ boardUuid,
+ )
+ }
+
+ // Detaches a pawn from the board
+ access(contract)
+ fun detachFromBoard(_ id: UInt64, _ board: Address, _ boardUuid: UInt64) {
+ let pawnRef = self.borrowFullWritablePawn(id)
+ ?? panic("Pawn not found")
+ assert(pawnRef.isOnBoard(), message: "Pawn should be on the board")
+ let position = pawnRef.borrowPosition() ?? panic("Pawn position not found")
+ assert(position.board.container == board, message: "Pawn should be on the board")
+ assert(position.board.id == boardUuid, message: "Pawn should be on the board")
+
+ let pawn <- self.owned.remove(key: id) ?? panic("Pawn not found")
+ let library = pawnRef.library
+ let uuid = pawnRef.uuid
+
+ remove PositionXY from pawn
+ remove Health from pawn
+ self.owned[uuid] <-! pawn
+
+ emit PawnDetachedFromBoard(
+ library,
+ self.owner?.address ?? panic("Player not exists"),
+ uuid,
+ board,
+ boardUuid,
+ )
+ }
+
+ // Returns a mutable reference to the pawn with the given ID
+ access(contract) view
+ fun borrowFullWritablePawn(_ id: UInt64): auth(Host, Player) &Pawn? {
+ return &self.owned[id]
+ }
+ }
+
+ // Creates a new player pocket
+ access(all) view
+ fun createPlayerPocket(): @PlayerPocket {
+ return <- create PlayerPocket()
+ }
+
+ // Returns a reference to the player's pocket
+ access(all) view
+ fun borrowPlayerPocket(_ address: Address): &PlayerPocket? {
+ return getAccount(address)
+ .capabilities
+ .get<&PlayerPocket>(self.pocketPublicPath)
+ .borrow()
+ }
+
+ // A struct to identify a pawn
+ access(all) struct PawnIdentifier {
+ let owner: Address
+ let id: UInt64
+
+ init(_ owner: Address, _ id: UInt64) {
+ self.owner = owner
+ self.id = id
+ }
+
+ // Returns a reference to the pawn
+ access(all) view
+ fun borrowPawn(): &Pawn? {
+ if let pocketRef = FGameMishal.borrowPlayerPocket(self.owner) {
+ return pocketRef.borrowPawn(self.id)
+ }
+ return nil
+ }
+
+ // Returns a mutable reference to the pawn
+ access(contract) view
+ fun borrowFullWritablePawn(): auth(Host) &Pawn? {
+ if let pocketRef = FGameMishal.borrowPlayerPocket(self.owner) {
+ return pocketRef.borrowFullWritablePawn(self.id)
+ }
+ return nil
+ }
+ }
+
+ // The HostBoard resource is used to store the pawns for some game
+ // This resource is stored in the host's account
+ access(all) resource HostBoard {
+ access(all)
+ let deadUnits: [PawnIdentifier]
+ access(all)
+ let leftUnits: [PawnIdentifier]
+ access(contract)
+ let participants: {Address: [UInt64]}
+ access(all)
+ var opened: Bool
+
+ init() {
+ self.participants = {}
+ self.deadUnits = []
+ self.leftUnits = []
+
+ self.opened = false
+ }
+
+ access(all) view
+ fun isActive(): Bool {
+ for participant in self.participants.keys {
+ if let pawns = self.participants[participant] {
+ if pawns.length > 0 {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ // Returns the number of participants in the board
+ access(all) view
+ fun getParticipantsLength(): Int {
+ return self.participants.length
+ }
+
+ // Returns the addresses of the participants in the board
+ access(all) view
+ fun getParticipants(): [Address] {
+ return self.participants.keys
+ }
+
+ // Returns the IDs of the pawns of the participant
+ access(all) view
+ fun getParticipantsPawnIDs(_ address: Address): [UInt64] {
+ return self.participants[address] ?? []
+ }
+
+ // Returns a mutable reference to the pawns of the participant
+ access(all)
+ fun borrowParticipantPawns(_ address: Address): [&Pawn] {
+ if let pocketRef = FGameMishal.borrowPlayerPocket(address) {
+ let ids = self.getParticipantsPawnIDs(address)
+ let pawns: [&Pawn] = []
+ for id in ids {
+ if let pawn = pocketRef.borrowPawn(id) {
+ pawns.append(pawn)
+ }
+ }
+ return pawns
+ }
+ return []
+ }
+
+ // Called when the board is opened
+ access(contract)
+ fun onOpening() {
+ pre {
+ !self.opened: "Board should not be opened"
+ }
+
+ let boardAddr = self.owner?.address ?? panic("Host not exists")
+ let boardUuid = self.uuid
+ // Attach the position to the units
+ for participant in self.participants.keys {
+ if let ids = self.borrowParticipantIds(participant) {
+ for id in ids {
+ self.attachPawn(participant, id)
+ }
+ }
+ }
+ }
+
+ // Called when the board is closed
+ access(contract)
+ fun onClosed() {
+ pre {
+ self.opened: "Board should be opened"
+ }
+
+ // Detach the position from the units
+ let boardAddr = self.owner?.address ?? panic("Host not exists")
+ let boardUuid = self.uuid
+ for participant in self.participants.keys {
+ if let ids = self.borrowParticipantIds(participant) {
+ for id in ids {
+ self.detachPawn(participant, id)
+ }
+ }
+ }
+ }
+
+ // Returns a mutable reference to the pawn of the participant
+ access(Host) view
+ fun borrowWritableParticipantPawn(_ address: Address, _ id: UInt64): auth(Host) &Pawn? {
+ if let pocketRef = FGameMishal.borrowPlayerPocket(address) {
+ return pocketRef.borrowFullWritablePawn(id)
+ }
+ return nil
+ }
+
+ // Inserts a pawn to the participant's pocket
+ access(Host)
+ fun insertPawn(_ address: Address, _ uuid: UInt64) {
+ let pocketRef = FGameMishal.borrowPlayerPocket(address)
+ ?? panic("Player pocket not found")
+ let pawn = pocketRef.borrowFullWritablePawn(uuid)
+ ?? panic("Pawn not found")
+
+ let participants = self.borrowAndEnsureParticipants(address)
+ assert(!participants.contains(uuid), message: "Pawn already exists")
+
+ participants.append(uuid)
+ // attach the pawn to the board
+ self.attachPawn(address, uuid)
+ }
+
+ // Removes a dead pawn from the host board
+ access(Host)
+ fun removeDeadPawn(_ address: Address, _ id: UInt64) {
+ if let pawn = PawnIdentifier(address, id).borrowFullWritablePawn() {
+ let health = pawn.borrowHealth()
+ // ensure pawn is dead
+ assert(health?.isDead() ?? false, message: "Pawn is not dead")
+
+ self.removePawn(pawn)
+ return
+ }
+ panic("Pawn not found")
+ }
+
+ // Removes an alive pawn from the host board
+ access(Host)
+ fun removeAlivePawn(_ address: Address, _ id: UInt64) {
+ if let pawn = PawnIdentifier(address, id).borrowFullWritablePawn() {
+ let health = pawn.borrowHealth()
+ // ensure pawn is alive
+ assert(!(health?.isDead() ?? false), message: "Pawn should be alive")
+
+ self.removePawn(pawn)
+ return
+ }
+ panic("Pawn not found")
+ }
+
+ // --- Internal Functions ---
+
+ // Returns a mutable reference to the board with the given UUID
+ access(contract) view
+ fun borrowInternalWritableBoard(): auth(Host) &HostBoard {
+ return &self
+ }
+
+ // ---- Private Functions ----
+
+ access(self)
+ fun removePawn(_ pawn: &Pawn) {
+ let pawnOwner = pawn.owner?.address ?? panic("Pawn owner not found")
+ let pawnId = pawn.uuid
+ let participants = self.borrowAndEnsureParticipants(pawnOwner)
+ if let index = participants.firstIndex(of: pawnId) {
+ let _ = participants.remove(at: index)
+
+ if let health = pawn.borrowHealth() {
+ if health.isDead() {
+ self.deadUnits.append(PawnIdentifier(pawnOwner, pawnId))
+ } else {
+ self.leftUnits.append(PawnIdentifier(pawnOwner, pawnId))
+ }
+ }
+
+ self.detachPawn(pawnOwner, pawnId)
+ }
+ }
+
+ access(self)
+ fun attachPawn(_ address: Address, _ uuid: UInt64) {
+ let pocketRef = FGameMishal.borrowPlayerPocket(address)
+ ?? panic("Player pocket not found")
+ assert(pocketRef.borrowPawn(uuid) != nil, message: "Pawn not found")
+
+ if !pocketRef.isPawnOnBoard(uuid) {
+ pocketRef.attachToBoard(uuid, self.getContainerAddress(), self.uuid)
+ }
+ }
+
+ access(self)
+ fun detachPawn(_ address: Address, _ uuid: UInt64) {
+ let pocketRef = FGameMishal.borrowPlayerPocket(address)
+ ?? panic("Player pocket not found")
+ assert(pocketRef.borrowPawn(uuid) != nil, message: "Pawn not found")
+
+ if pocketRef.isPawnOnBoard(uuid) {
+ pocketRef.detachFromBoard(uuid, self.getContainerAddress(), self.uuid)
+ }
+ }
+
+ access(self) view
+ fun getContainerAddress(): Address {
+ return self.owner?.address ?? panic("Host not exists")
+ }
+
+ access(self)
+ fun borrowAndEnsureParticipants(_ address: Address): auth(Mutate) &[UInt64] {
+ if let participants = self.borrowParticipantIds(address) {
+ return participants
+ } else {
+ self.participants[address] = []
+ return &self.participants[address]!
+ }
+ }
+
+ access(self) view
+ fun borrowParticipantIds(_ address: Address): auth(Mutate) &[UInt64]? {
+ if let ids = &self.participants[address] as auth(Mutate) &[UInt64]? {
+ return ids
+ }
+ return nil
+ }
+ }
+
+ access(all) resource interface HostBoardContainer {
+ // Returns a reference to the board with the given UUID
+ access(all) view
+ fun borrowBoard(_ uuid: UInt64): &HostBoard? {
+ return self.borrowWritableBoard(uuid)
+ }
+
+ access(all) view
+ fun getBoardIds(): [UInt64]
+
+ // Returns a mutable reference to the board with the given UUID
+ access(Host) view
+ fun borrowWritableBoard(_ uuid: UInt64): auth(Host) &HostBoard?
+
+ // Stores a board in the container
+ access(Host)
+ fun storeBoard(_ board: @HostBoard)
+
+ // Creates a new host board
+ access(Host)
+ fun createNewBoard(): UInt64 {
+ let newBoard <- create HostBoard()
+ let uuid = newBoard.uuid
+ self.storeBoard(<- newBoard)
+
+ emit HostBoardCreated(
+ self.owner?.address ?? panic("Host not exists"),
+ uuid,
+ )
+ return uuid
+ }
+ }
+
+ access(all) struct BoardIdentifier {
+ let container: Address
+ let id: UInt64
+
+ view init(
+ _ container: Address,
+ _ id: UInt64,
+ ) {
+ self.container = container
+ self.id = id
+ }
+
+ access(all) view
+ fun toString(): String {
+ return self.container.toString().concat("#").concat(self.id.toString())
+ }
+
+ access(all) view
+ fun borrowBoardContainer(): &{HostBoardContainer}? {
+ return getAccount(self.container)
+ .capabilities
+ .get<&{HostBoardContainer}>(FGameMishal.hostBoardContainerPublicPath)
+ .borrow()
+ }
+
+ access(all) view
+ fun borrowBoard(): &HostBoard? {
+ if let container = self.borrowBoardContainer() {
+ return container.borrowBoard(self.id)
+ }
+ return nil
+ }
+ }
+
+ // The HostOperator resource is used to manage the active containers of the host
+ // This resource is stored in the host's account
+ access(all) resource HostOperator {
+ access(contract)
+ let boards: {Address: [UInt64]}
+ access(contract)
+ let activeBoards: [BoardIdentifier]
+ access(contract)
+ var pawnSpawner: Capability?
+ access(contract)
+ let boardContainers: {Address: Capability}
+
+ view init() {
+ self.boards = {}
+ self.activeBoards = []
+ self.pawnSpawner = nil
+ self.boardContainers = {}
+ }
+
+ access(all) view
+ fun getActiveBoardsLength(): Int {
+ return self.activeBoards.length
+ }
+
+ access(all) view
+ fun getActiveBoards(): [BoardIdentifier] {
+ return self.activeBoards
+ }
+
+ // Updates the pawn spawner
+ access(Host)
+ fun updatePawnSpawner(_ pawnSpawner: Capability) {
+ pre {
+ pawnSpawner.borrow() != nil: "Pawn spawner is not valid"
+ }
+ self.pawnSpawner = pawnSpawner
+
+ emit HostOperatorSpawnerUpdated(
+ self.owner?.address ?? panic("Host not exists"),
+ self.uuid,
+ pawnSpawner.address,
+ pawnSpawner.borrow()?.uuid ?? panic("Pawn spawner is not valid"),
+ )
+ }
+
+ access(Host)
+ fun addBoardContainer(_ cap: Capability) {
+ pre {
+ cap.borrow() != nil: "Board container is not valid"
+ self.boardContainers[cap.address] == nil: "Board container already exists"
+ }
+
+ let addr = cap.address
+ self.boardContainers[addr] = cap
+
+ emit HostOperatorBoardContainerAdded(
+ self.owner?.address ?? panic("Host not exists"),
+ self.uuid,
+ addr,
+ )
+ }
+
+ access(Host)
+ fun removeBoardContainer(_ addr: Address) {
+ pre {
+ self.boardContainers.keys.contains(addr): "Board container not found"
+ }
+
+ // check if the board container is empty
+ let ids = self.borrowBoardContainerIds(addr)
+ ?? panic("Board container not found")
+
+ // Close all boards
+ for id in ids {
+ let board = self.borrowInternalWritableBoard(addr, id)
+ ?? panic("Board not found")
+ if board.isActive() {
+ self.closeBoard(addr, id)
+ }
+ }
+
+ // Remove the board container
+ let _ = self.boardContainers.remove(key: addr)
+
+ // emit event
+ emit HostOperatorBoardContainerRemoved(
+ self.owner?.address ?? panic("Host not exists"),
+ self.uuid,
+ addr,
+ )
+ }
+
+ // Returns a mutable reference to the pawn spawner
+ access(Host) view
+ fun borrowPawnSpawner(): auth(Host) &{PawnSpawner}? {
+ if let spwaner = self.pawnSpawner?.borrow() {
+ return spwaner
+ }
+ return nil
+ }
+
+ // Returns a mutable reference to the board with the given container and id
+ access(Host) view
+ fun borrowWritableBoard(_ container: Address, _ id: UInt64): auth(Host) &HostBoard? {
+ return self.borrowInternalWritableBoard(container, id)
+ }
+
+ // Opens a board
+ access(Host)
+ fun openBoard(_ container: Address, _ id: UInt64) {
+ let containerRef = self.borrowBoardContainer(container)
+ ?? panic("Board container not found")
+ let board = containerRef.borrowWritableBoard(id)
+ ?? panic("Board not found")
+
+ if self.boards[container] == nil {
+ self.boards[container] = []
+ }
+ let ids = self.borrowBoardContainerIds(container)
+ ?? panic("Board container not found")
+ assert(!ids.contains(id), message: "Board has already added")
+
+ ids.append(id)
+ self.activeBoards.append(BoardIdentifier(container, id))
+
+ // open the board
+ board.onOpening()
+
+ emit HostBoardOpened(self.owner?.address ?? panic("Host not exists"), container, id)
+ }
+
+ // Closes a board
+ access(Host)
+ fun closeBoard(_ container: Address, _ id: UInt64) {
+ let board = self.borrowInternalWritableBoard(container, id)
+ ?? panic("Board not found")
+
+ assert(board.isActive() == false, message: "Board should be inactive")
+
+ let ids = self.borrowBoardContainerIds(container)
+ ?? panic("Board container not found")
+ assert(ids.contains(id), message: "Board not found")
+
+ // remove the board from the container
+ let index = ids.firstIndex(of: id)
+ ?? panic("Board not found")
+ let _ = ids.remove(at: index)
+
+ // remove the board from the active boards
+ let len = self.activeBoards.length
+ var i = 0
+ while i < len {
+ if self.activeBoards[i].container == container && self.activeBoards[i].id == id {
+ let _ = self.activeBoards.remove(at: i)
+ break
+ }
+ i = i + 1
+ }
+
+ // close the board
+ board.onClosed()
+
+ emit HostBoardClosed(self.owner?.address ?? panic("Host not exists"), container, id)
+ }
+
+ // Returns a mutable reference to the board container with the given address
+ access(self) view
+ fun borrowBoardContainer(_ container: Address): auth(Host) &{HostBoardContainer}? {
+ if let cap = self.boardContainers[container] {
+ return cap.borrow()
+ }
+ return nil
+ }
+
+ // Returns a mutable reference to the board with the given container and id
+ access(self) view
+ fun borrowInternalWritableBoard(_ container: Address, _ id: UInt64): auth(Host) &HostBoard? {
+ if let ids = self.borrowBoardContainerIds(container) {
+ if ids.contains(id) {
+ if let container = self.borrowBoardContainer(container) {
+ return container.borrowWritableBoard(id)
+ }
+ }
+ }
+ return nil
+ }
+
+ // Returns a mutable reference to the board with the given container and id
+ access(self) view
+ fun borrowBoardContainerIds(_ container: Address): auth(Mutate) &[UInt64]? {
+ if let ids = &self.boards[container] as auth(Mutate) &[UInt64]? {
+ return ids
+ }
+ return nil
+ }
+ }
+
+ // Creates a new host operator
+ access(all) view
+ fun createHostOperator(): @HostOperator {
+ return <- create HostOperator()
+ }
+
+ // Returns a reference to the host's operator
+ access(all) view
+ fun borrowHostOperator(_ host: Address): &HostOperator? {
+ return getAccount(host)
+ .capabilities
+ .get<&HostOperator>(self.hostOperatorPublicPath)
+ .borrow()
+ }
+
+ // ---- Public Functions ----
+
+ // Creates a new library
+ access(all)
+ fun createLibrary(): @Library {
+ return <- create Library()
+ }
+
+ // Returns a reference to the library with the given address
+ access(all) view
+ fun borrowLibrary(_ address: Address): &Library? {
+ return getAccount(address).capabilities
+ .get<&Library>(self.libraryPublicPath)
+ .borrow()
+ }
+
+ // Returns a reference to the most popular library
+ access(all) view
+ fun borrowPopularLibrary(): &Library? {
+ // Borrow the libraray with the highest item count
+ var maxItemCount: UInt64 = 0
+ var popularLibrary: &Library? = nil
+
+ let keys = self.libraryItems.keys
+ for key in keys {
+ if self.libraryItems[key]! > maxItemCount {
+ maxItemCount = self.libraryItems[key]!
+ if let library = self.borrowLibrary(key) {
+ popularLibrary = library
+ }
+ }
+ }
+ return popularLibrary
+ }
+
+ init() {
+ self.libraryItems = {}
+
+ let identifier = "FGameMishal_".concat(self.account.address.toString())
+ self.libraryStoragePath = StoragePath(identifier: identifier.concat("_Library"))!
+ self.libraryPublicPath = PublicPath(identifier: identifier.concat("_Library"))!
+
+ self.pocketStoragePath = StoragePath(identifier: identifier.concat("_PlayerPocket"))!
+ self.pocketPublicPath = PublicPath(identifier: identifier.concat("_PlayerPocket"))!
+
+ self.hostOperatorStoragePath = StoragePath(identifier: identifier.concat("_HostOperator"))!
+ self.hostOperatorPublicPath = PublicPath(identifier: identifier.concat("_HostOperator"))!
+
+ self.hostBoardContainerPublicPath = PublicPath(identifier: identifier.concat("_HostBoardContainer"))!
+ }
+}
diff --git a/cadence/contracts/FGameMishalBattleField.cdc b/cadence/contracts/FGameMishalBattleField.cdc
new file mode 100644
index 00000000..6719222c
--- /dev/null
+++ b/cadence/contracts/FGameMishalBattleField.cdc
@@ -0,0 +1,31 @@
+/**
+> Author: Fixes Lab
+
+# FGameMishalBattleField
+
+This contract is a battlefield game contract. It allows users to buy tickets to setup characters and battle with each other.
+
+*/
+import "Burner"
+// Fixes Imports
+import "Fixes"
+import "FixesHeartbeat"
+
+access(all) contract FGameMishalBattleField {
+
+ access(all) entitlement CommanderControl
+ access(all) entitlement SessionManage
+ access(all) entitlement Creator
+
+ // The Commander resource is refered to as the player in the game.
+ access(all) resource Commander {
+ }
+
+ // The Session resource is refered to as the battle in the game.
+ access(all) resource Session {
+ }
+
+ // The World resource is refered to as the world in the game.
+ access(all) resource World {
+ }
+}
\ No newline at end of file
diff --git a/cadence/scripts/boardgame/.gitkeep b/cadence/scripts/boardgame/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/cadence/transactions/boardgame/.gitkeep b/cadence/transactions/boardgame/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/flow.json b/flow.json
index 5223f276..c54d59c1 100644
--- a/flow.json
+++ b/flow.json
@@ -35,6 +35,20 @@
"testnet": "b7248baa24a95c3f"
}
},
+ "FGameMishal": {
+ "source": "cadence/contracts/FGameMishal.cdc",
+ "aliases": {
+ "mainnet": "d2abb5dbf5e08666",
+ "testnet": "b7248baa24a95c3f"
+ }
+ },
+ "FGameMishalBattleField": {
+ "source": "cadence/contracts/FGameMishalBattleField.cdc",
+ "aliases": {
+ "mainnet": "d2abb5dbf5e08666",
+ "testnet": "b7248baa24a95c3f"
+ }
+ },
"FRC20AccountsPool": {
"source": "cadence/contracts/FRC20AccountsPool.cdc",
"aliases": {
@@ -1071,7 +1085,9 @@
"FixesFungibleToken",
"FRC20FungibleToken",
"FungibleTokenManager",
- "FixesTVL"
+ "FixesTVL",
+ "FGameMishal",
+ "FGameMishalBattleField"
]
},
"mainnet": {
@@ -1113,7 +1129,9 @@
"FixesFungibleToken",
"FRC20FungibleToken",
"FungibleTokenManager",
- "FixesTVL"
+ "FixesTVL",
+ "FGameMishal",
+ "FGameMishalBattleField"
]
},
"testnet": {
@@ -1155,7 +1173,9 @@
"FixesFungibleToken",
"FRC20FungibleToken",
"FungibleTokenManager",
- "FixesTVL"
+ "FixesTVL",
+ "FGameMishal",
+ "FGameMishalBattleField"
]
}
}