From 26cd5459404a9c4e370bc560888bf653a58dcd9e Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Thu, 15 May 2025 18:37:49 +0800 Subject: [PATCH 01/45] feat: add FGameBattleField contract and update flow.json --- cadence/contracts/FGameBattleField.cdc | 18 ++++++++++++++++++ flow.json | 16 +++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 cadence/contracts/FGameBattleField.cdc diff --git a/cadence/contracts/FGameBattleField.cdc b/cadence/contracts/FGameBattleField.cdc new file mode 100644 index 0000000..5036406 --- /dev/null +++ b/cadence/contracts/FGameBattleField.cdc @@ -0,0 +1,18 @@ +/** +> Author: Fixes Lab + +# FGameBattleField + +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 FGameBattleField { + + access(all) entitlement Admin + +} \ No newline at end of file diff --git a/flow.json b/flow.json index 5223f27..b268ad1 100644 --- a/flow.json +++ b/flow.json @@ -14,6 +14,13 @@ "testnet": "b7248baa24a95c3f" } }, + "FGameBattleField": { + "source": "cadence/contracts/FGameBattleField.cdc", + "aliases": { + "mainnet": "d2abb5dbf5e08666", + "testnet": "b7248baa24a95c3f" + } + }, "FGameLottery": { "source": "cadence/contracts/FGameLottery.cdc", "aliases": { @@ -1071,7 +1078,8 @@ "FixesFungibleToken", "FRC20FungibleToken", "FungibleTokenManager", - "FixesTVL" + "FixesTVL", + "FGameBattleField" ] }, "mainnet": { @@ -1113,7 +1121,8 @@ "FixesFungibleToken", "FRC20FungibleToken", "FungibleTokenManager", - "FixesTVL" + "FixesTVL", + "FGameBattleField" ] }, "testnet": { @@ -1155,7 +1164,8 @@ "FixesFungibleToken", "FRC20FungibleToken", "FungibleTokenManager", - "FixesTVL" + "FixesTVL", + "FGameBattleField" ] } } From 9cc95e57976626dd842eaa06e046816663bb6a17 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Thu, 15 May 2025 21:05:56 +0800 Subject: [PATCH 02/45] feat: enhance FGameBattleField contract with new entitlements and resources --- cadence/contracts/FGameBattleField.cdc | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cadence/contracts/FGameBattleField.cdc b/cadence/contracts/FGameBattleField.cdc index 5036406..7658406 100644 --- a/cadence/contracts/FGameBattleField.cdc +++ b/cadence/contracts/FGameBattleField.cdc @@ -13,6 +13,23 @@ import "FixesHeartbeat" access(all) contract FGameBattleField { - access(all) entitlement Admin + access(all) entitlement CommanderControl + access(all) entitlement SessionManage + access(all) entitlement Creator + // The Pawn resource is refered to as the character in the game. + access(all) resource Pawn { + } + + // 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 From 8cc26788aeb7d9f5aaa47fa7a4ce59cbbd7baa2c Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 16 May 2025 17:46:24 +0800 Subject: [PATCH 03/45] feat: add FGameMishal and FGameMishalBattleField contracts, update flow.json --- cadence/contracts/FGameMishal.cdc | 21 +++++++++++++ ...leField.cdc => FGameMishalBattleField.cdc} | 8 ++--- flow.json | 30 ++++++++++++------- 3 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 cadence/contracts/FGameMishal.cdc rename cadence/contracts/{FGameBattleField.cdc => FGameMishalBattleField.cdc} (81%) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc new file mode 100644 index 0000000..c9a00ea --- /dev/null +++ b/cadence/contracts/FGameMishal.cdc @@ -0,0 +1,21 @@ +/** +> Author: Fixes Lab + +# FGameMishal + +This contract is used to define the basic game elements. + +*/ +import "Burner" +// Fixes Imports +import "Fixes" +import "FixesHeartbeat" + +access(all) contract FGameMishal { + + // The Pawn resource is refered to as the character in the game. + access(all) resource Pawn { + } + + +} diff --git a/cadence/contracts/FGameBattleField.cdc b/cadence/contracts/FGameMishalBattleField.cdc similarity index 81% rename from cadence/contracts/FGameBattleField.cdc rename to cadence/contracts/FGameMishalBattleField.cdc index 7658406..6719222 100644 --- a/cadence/contracts/FGameBattleField.cdc +++ b/cadence/contracts/FGameMishalBattleField.cdc @@ -1,7 +1,7 @@ /** > Author: Fixes Lab -# FGameBattleField +# FGameMishalBattleField This contract is a battlefield game contract. It allows users to buy tickets to setup characters and battle with each other. @@ -11,16 +11,12 @@ import "Burner" import "Fixes" import "FixesHeartbeat" -access(all) contract FGameBattleField { +access(all) contract FGameMishalBattleField { access(all) entitlement CommanderControl access(all) entitlement SessionManage access(all) entitlement Creator - // The Pawn resource is refered to as the character in the game. - access(all) resource Pawn { - } - // The Commander resource is refered to as the player in the game. access(all) resource Commander { } diff --git a/flow.json b/flow.json index b268ad1..c54d59c 100644 --- a/flow.json +++ b/flow.json @@ -14,13 +14,6 @@ "testnet": "b7248baa24a95c3f" } }, - "FGameBattleField": { - "source": "cadence/contracts/FGameBattleField.cdc", - "aliases": { - "mainnet": "d2abb5dbf5e08666", - "testnet": "b7248baa24a95c3f" - } - }, "FGameLottery": { "source": "cadence/contracts/FGameLottery.cdc", "aliases": { @@ -42,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": { @@ -1079,7 +1086,8 @@ "FRC20FungibleToken", "FungibleTokenManager", "FixesTVL", - "FGameBattleField" + "FGameMishal", + "FGameMishalBattleField" ] }, "mainnet": { @@ -1122,7 +1130,8 @@ "FRC20FungibleToken", "FungibleTokenManager", "FixesTVL", - "FGameBattleField" + "FGameMishal", + "FGameMishalBattleField" ] }, "testnet": { @@ -1165,7 +1174,8 @@ "FRC20FungibleToken", "FungibleTokenManager", "FixesTVL", - "FGameBattleField" + "FGameMishal", + "FGameMishalBattleField" ] } } From 2f3b18395b485621a1caaf2f822edf51828617e8 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sat, 17 May 2025 00:24:02 +0800 Subject: [PATCH 04/45] feat: expand FGameMishal contract with new game elements, resources, and functionalities; update FGameMishalBattleField with Pawn resource --- cadence/contracts/FGameMishal.cdc | 1125 +++++++++++++++++- cadence/contracts/FGameMishalBattleField.cdc | 5 + 2 files changed, 1129 insertions(+), 1 deletion(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index c9a00ea..4186c2d 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -4,6 +4,7 @@ # FGameMishal This contract is used to define the basic game elements. +The basic elements of a Mishal Game will be defined here. */ import "Burner" @@ -12,10 +13,1132 @@ import "Fixes" import "FixesHeartbeat" access(all) contract FGameMishal { + // Entitlements for the Editor role + access(all) entitlement Editor; + // Entitlements for the Manage role (Like the host of the game) + access(all) entitlement Manage; + + // ----- Events ----- + + access(all) event LibrarySettingChanged(_ library: Address, key: UInt8, value: Int64) + access(all) event LibraryObjectAdded(_ library: Address, _ uuid: UInt64, _ name: String) + access(all) event LibraryItemAdded(_ library: Address, _ uuid: UInt64, _ name: String) + access(all) event LibraryAbilityAdded(_ library: Address, _ uuid: UInt64, _ name: String) + access(all) event LibraryShapeAdded(_ library: Address, _ uuid: UInt64, _ name: String) + access(all) event LibraryFeatureAdded(_ library: Address, _ uuid: UInt64, _ name: String) + access(all) event LibraryCreatureAdded(_ library: Address, _ uuid: UInt64, _ name: String) + + // ----- 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 + + // ----- Resources ----- + + access(all) enum LibrarySettings: UInt8 { + access(all) case INIT_POTENTIALITY + } + + 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(all) let settings: {LibrarySettings: Int64} + access(all) let objects: @{UInt64: Object} + access(all) let items: @{UInt64: Item} + access(all) let abilities: @{UInt64: Ability} + access(all) let shapes: @{UInt64: Shape} + access(all) let features: @{UInt64: Feature} + access(all) let creatures: @{UInt64: Creature} + access(self) let objectsNameToUID: {String: UInt64} + access(self) let itemsNameToUID: {String: UInt64} + access(self) let abilitiesNameToUID: {String: UInt64} + access(self) let shapesNameToUID: {String: UInt64} + access(self) let featuresNameToUID: {String: UInt64} + access(self) let creaturesNameToUID: {String: UInt64} + + init() { + self.settings = { + LibrarySettings.INIT_POTENTIALITY: 36 + } + self.objects <- {} + self.items <- {} + self.abilities <- {} + self.shapes <- {} + self.features <- {} + self.creatures <- {} + self.objectsNameToUID = {} + self.itemsNameToUID = {} + self.abilitiesNameToUID = {} + self.shapesNameToUID = {} + self.featuresNameToUID = {} + self.creaturesNameToUID = {} + } + + 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.objectsNameToUID[object.name] == nil: + "Object name already exists" + } + let uuid = object.uuid + let name = object.name + self.objectsNameToUID[name] = uuid + self.objects[uuid] <-! object + + emit LibraryObjectAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + } + + access(Editor) + fun addItem(item: @Item) { + pre { + self.itemsNameToUID[item.name] == nil: + "Item name already exists" + } + let uuid = item.uuid + let name = item.name + self.itemsNameToUID[name] = uuid + self.items[uuid] <-! item + + emit LibraryItemAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + } + + access(Editor) + fun addAbility(ability: @Ability) { + pre { + self.abilitiesNameToUID[ability.name] == nil: + "Ability name already exists" + } + let uuid = ability.uuid + let name = ability.name + self.abilitiesNameToUID[name] = uuid + self.abilities[uuid] <-! ability + + emit LibraryAbilityAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + } + + access(Editor) + fun addShape(shape: @Shape) { + pre { + self.shapesNameToUID[shape.name] == nil: + "Shape name already exists" + } + let uuid = shape.uuid + let name = shape.name + self.shapesNameToUID[name] = uuid + self.shapes[uuid] <-! shape + + emit LibraryShapeAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + } + + access(Editor) + fun addFeature(feature: @Feature) { + pre { + self.featuresNameToUID[feature.name] == nil: + "Feature name already exists" + } + let uuid = feature.uuid + let name = feature.name + self.featuresNameToUID[name] = uuid + self.features[uuid] <-! feature + + emit LibraryFeatureAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + } + + access(Editor) + fun addCreature(creature: @Creature) { + pre { + self.creaturesNameToUID[creature.name] == nil: + "Creature name already exists" + } + let uuid = creature.uuid + let name = creature.name + self.creaturesNameToUID[name] = uuid + self.creatures[uuid] <-! creature + + emit LibraryCreatureAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + } + + // -------- Public Functions -------- + + access(all) view + fun borrowObject(_ uuid: UInt64): &Object? { + return &self.objects[uuid] + } + + access(all) view + fun borrowObjectByName(_ name: String): &Object? { + if let uuid = self.objectsNameToUID[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.itemsNameToUID[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.abilitiesNameToUID[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.shapesNameToUID[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.featuresNameToUID[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.creaturesNameToUID[name] { + return self.borrowCreature(uuid) + } + return nil + } + } + + // ------------ Library Entities ------------ + + access(all) struct interface Copyable { + access(all) fun copy(): {Copyable} + } + + access(all) struct Potentiality: Copyable { + access(all) let initial: Int64 + access(all) var current: Int64 + access(all) var used: Int64 + + view init(initial: Int64) { + self.initial = initial + self.current = initial + self.used = 0 + } + + access(all) fun copy(): {Copyable} { return self } + + access(contract) + fun add(amount: Int64) { + self.current = self.current + amount + } + + access(contract) + fun use(amount: Int64) { + pre { + self.current >= amount: + "Not enough potentiality" + } + self.current = self.current - amount + self.used = self.used + 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(contract) + fun setStrength(strength: Int64) { + self.strength = strength + } + + access(contract) + fun addStrength(strength: Int64) { + self.strength = self.strength + strength + } + + access(contract) + fun setVitality(vitality: Int64) { + self.vitality = vitality + } + + access(contract) + fun addVitality(vitality: Int64) { + self.vitality = self.vitality + vitality + } + + access(contract) + fun setSpirit(spirit: Int64) { + self.spirit = spirit + } + + access(contract) + fun addSpirit(spirit: Int64) { + self.spirit = self.spirit + spirit + } + } + + 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(contract) + fun setPhysical(physical: Int64) { + self.physical = physical + } + + access(contract) + fun addPhysical(physical: Int64) { + self.physical = self.physical + physical + } + + access(contract) + fun setEndurance(endurance: Int64) { + self.endurance = endurance + } + + access(contract) + fun addEndurance(endurance: Int64) { + self.endurance = self.endurance + endurance + } + + access(contract) + fun setResistance(resistance: Int64) { + self.resistance = resistance + } + + access(contract) + fun addResistance(resistance: Int64) { + self.resistance = self.resistance + resistance + } + } + + access(all) resource interface Nameable { + access(all) let name: String + } + + // The PotentialityCarrier resource interface is used to get the potentiality of the entry. + access(all) resource interface PotentialityCarrier { + access(all) view fun getInitialPotentiality(): Int64 { + return self.borrowPotentiality()?.initial ?? 0 + } + + access(all) view fun getCurrentPotentiality(): Int64 { + return self.borrowPotentiality()?.current ?? 0 + } + + access(all) view fun getUsedPotentiality(): Int64 { + return self.borrowPotentiality()?.used ?? 0 + } + + access(all) view fun borrowPotentiality(): &Potentiality? + } + + // The AttributeCarrier resource interface is used to get the attributes of the entry. + access(all) resource interface AttributeCarrier { + 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 + } + + access(all) view fun borrowAttributes(): &Attributes? + } + + // The DefenceCarrier resource interface is used to get the defence of the entry. + access(all) resource interface DefenceCarrier { + 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 + } + + access(all) view fun borrowDefence(): &Defence? + } + + // The StatusCarrier resource interface is used to get the status of the entry. + access(all) resource interface StatusCarrier: AttributeCarrier, DefenceCarrier { + // Main attributes of the character + access(all) let attributes: Attributes + // The defence of the character + access(all) let defence: Defence + + access(all) view fun borrowAttributes(): &Attributes? { + return &self.attributes as &Attributes + } + + access(all) view fun borrowDefence(): &Defence? { + return &self.defence as &Defence + } + } + + 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(Manage) + fun setValue(value: UFix64) { + self.value = value + } + + access(Manage) + fun addValue(value: UFix64) { + 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(Manage) + fun addEffect(effect: String) { + self.effects.append(effect) + } + + access(Manage) + fun removeEffect(effect: String) { + if let index = self.effects.firstIndex(of: effect) { + let _ = self.effects.remove(at: index) + } + } + } + + access(all) resource Object: DefenceCarrier, ValueCarrier, Nameable { + access(all) let name: 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, + defence: Defence, + value: UFix64? + ) { + self.name = name + self.defence = defence + self.value = value + } + + access(all) view fun borrowDefence(): &Defence? { + return &self.defence as &Defence + } + } + + access(all) resource Item: StatusCarrier, ValueCarrier, EffectsCarrier, Nameable { + access(all) let name: 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 effects of the item + access(all) let effects: [String] + // The slots occupied by the item + access(all) let slotsOccupied: [EquipSlot] + // The slots provided by the item + access(all) let slotsProvided: {EquipSlot: UInt8} + + view init( + name: String, + value: UFix64?, + attributes: Attributes, + defence: Defence, + effects: [String], + slotsOccupied: [EquipSlot], + slotsProvided: {EquipSlot: UInt8}, + ) { + self.name = name + self.value = value + self.attributes = attributes + self.defence = defence + self.effects = effects + self.slotsOccupied = slotsOccupied + self.slotsProvided = slotsProvided + } + } + + access(all) resource Ability: AttributeCarrier, EffectsCarrier, Nameable { + access(all) let name: String + access(all) let level: UInt64 + access(all) let occupy: Attributes + access(all) let effects: [String] + + view init( + level: UInt64, + name: String, + occupy: Attributes, + effects: [String] + ) { + self.name = name + self.level = level + self.occupy = occupy + self.effects = effects + } + + access(all) view fun borrowAttributes(): &Attributes? { + return &self.occupy as &Attributes + } + } + + // The CreatureSettingsCarrier resource interface is used to get the settings of the creature. + access(all) resource interface CreatureSettingsCarrier { + access(all) let settings: {CreatureSettings: Int64} + + access(all) view + fun getSetting(_ setting: CreatureSettings): Int64? { + return self.settings[setting] + } + + access(all) view + fun hasSettings(): Bool { + return self.settings.length > 0 + } + } + + access(all) resource Shape: CreatureSettingsCarrier, Nameable { + access(all) let name: String + access(all) let settings: {CreatureSettings: Int64} + access(all) let slotsAvailable: {EquipSlot: UInt8} + + view init( + name: String, + bodySize: Int64, + occupyRange: Int64, + moveSpeed: Int64, + perceptionRange: Int64, + slotsAvailable: {EquipSlot: UInt8 } + ) { + self.name = name + 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 + } + } + + // The ShapeCarrier resource interface is used to get the shape of the creature. + access(all) resource interface ShapeOverrides: CreatureSettingsCarrier { + access(all) let settings: {CreatureSettings: Int64} + + access(all) view fun borrowShape(): &Shape? + + access(all) view + fun getGender(): Int64? { + if let gender = self.settings[CreatureSettings.GENDER] { + return gender + } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.GENDER) + } + return nil + } + + access(all) view + fun getForm(): Int64? { + if let form = self.settings[CreatureSettings.FORM] { + return form + } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.FORM) + } + return nil + } + + access(all) view + fun getSize(): Int64? { + if let size = self.settings[CreatureSettings.SIZE] { + return size + } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.SIZE) + } + return nil + } + + access(all) view + fun getMoveSpeed(): Int64? { + if let moveSpeed = self.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? { + if let perceptionRange = self.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? { + if let occupyRange = self.settings[CreatureSettings.OCCUPY_RANGE] { + return occupyRange + } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.OCCUPY_RANGE) + } + return nil + } + } + + // 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 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") + } + } + + access(all) resource interface AbilitiesCarrier { + access(all) let abilities: [EntryIdentifier] + + access(all) + fun borrowAbilities(): [&Ability] { + return self.abilities + .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.abilities.length > 0 + } + } + + access(all) resource interface ItemsCarrier { + access(all) let items: [EntryIdentifier] + + access(all) + fun borrowItems(): [&Item] { + return self.items + .map(view fun (_ x: EntryIdentifier): &Item? { + return x.borrowItem() + }) + .filter(view fun (_ x: &Item?): Bool { + return x != nil + }) + .map(view fun (_ x: &Item?): &Item { + return x! + }) + } + + access(all) view + fun hasItems(): Bool { + return self.items.length > 0 + } + } + + access(all) resource interface ShapeCarrier: ShapeOverrides, Nameable { + access(all) let name: String + access(all) let shape: EntryIdentifier? + + access(all) view + fun borrowShape(): &Shape? { + if let shape = self.shape { + return shape.borrowShape() + } + return nil + } + + access(all) view + fun hasShape(): Bool { + return self.shape != nil + } + } + + // The Feature resource is used to define the features of the entry. + access(all) resource Feature: Nameable, AbilitiesCarrier, ItemsCarrier, ShapeCarrier, EffectsCarrier { + access(all) let name: String + access(all) let attributes: Attributes? + access(all) let defence: Defence? + access(all) let potentiality: Potentiality? + access(all) let effects: [String] + access(all) let abilities: [EntryIdentifier] + access(all) let items: [EntryIdentifier] + access(all) let shape: EntryIdentifier? + access(all) let settings: {CreatureSettings: Int64} + + view init( + name: String, + attributes: Attributes?, + defence: Defence?, + potentiality: Potentiality?, + shape: EntryIdentifier?, + abilities: [EntryIdentifier], + items: [EntryIdentifier], + effects: [String], + settings: {CreatureSettings: Int64} + ) { + self.name = name + self.attributes = attributes + self.defence = defence + self.potentiality = potentiality + self.shape = shape + self.abilities = abilities + self.items = items + self.effects = effects + self.settings = settings + + // check identifiers + if self.shape != nil { + assert(self.shape!.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") + } + 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") + } + } + + access(all) view + fun hasAttributes(): Bool { + return self.attributes != nil + } + + access(all) view + fun hasDefence(): Bool { + return self.defence != nil + } + + access(all) view + fun hasPotentiality(): Bool { + return self.potentiality != nil + } + } + + access(all) resource interface FeaturesCarrier { + access(all) let features: [EntryIdentifier] + + access(all) + fun borrowFeatures(): [&Feature] { + return self.features + .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.features.length > 0 + } + } + + access(all) resource interface LiveStatusCarrier: StatusCarrier, PotentialityCarrier { + access(all) let potentiality: Potentiality + + access(all) view + fun borrowPotentiality(): &Potentiality? { + return &self.potentiality as &Potentiality + } + } + + access(all) resource interface CreatureInterface: LiveStatusCarrier, FeaturesCarrier, AbilitiesCarrier, ItemsCarrier, ShapeOverrides { + // Main attributes of the character + access(all) let attributes: Attributes + // The defence of the character + access(all) let defence: Defence + // The potentiality of the character + access(all) let potentiality: Potentiality + // The features of the character + access(all) let features: [EntryIdentifier] + // The abilities of the character + access(all) let abilities: [EntryIdentifier] + // The items of the character + access(all) let items: [EntryIdentifier] + // The shape of the character + access(all) let shape: EntryIdentifier + // The settings of the character + access(all) let settings: {CreatureSettings: Int64} + + access(all) view + fun borrowShape(): &Shape? { + return self.shape.borrowShape() + } + } + + access(all) resource Creature: Nameable, CreatureInterface { + access(all) let name: String + // Main attributes of the character + access(all) let attributes: Attributes + // The defence of the character + access(all) let defence: Defence + // The potentiality of the character + access(all) let potentiality: Potentiality + // The features of the character + access(all) let features: [EntryIdentifier] + // The abilities of the character + access(all) let abilities: [EntryIdentifier] + // The items of the character + access(all) let items: [EntryIdentifier] + // The shape of the character + access(all) let shape: EntryIdentifier + // The settings of the character + access(all) let settings: {CreatureSettings: Int64} + + view init( + name: String, + attributes: Attributes, + defence: Defence, + potentiality: Potentiality, + features: [EntryIdentifier], + abilities: [EntryIdentifier], + items: [EntryIdentifier], + shape: EntryIdentifier, + settings: {CreatureSettings: Int64} + ) { + self.name = name + self.attributes = attributes + self.defence = defence + self.potentiality = potentiality + self.features = features + self.abilities = abilities + self.items = items + self.shape = shape + self.settings = settings + + + // check identifiers + assert(self.shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") + for featureIdentifier in features { + assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") + } + 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") + } + } + } + + // ------------ Player ------------ + + // The Clonable resource inteface is used to clone the entry. + access(all) resource interface Clonable { + access(all) let identifier: EntryIdentifier + access(Manage) fun clone(): @{Clonable} + } // The Pawn resource is refered to as the character in the game. - access(all) resource Pawn { + access(all) resource Pawn: CreatureInterface { + // Main attributes of the character + access(all) let attributes: Attributes + // The defence of the character + access(all) let defence: Defence + // The potentiality of the character + access(all) let potentiality: Potentiality + // The features of the character + access(all) let features: [EntryIdentifier] + // The abilities of the character + access(all) let abilities: [EntryIdentifier] + // The items of the character + access(all) let items: [EntryIdentifier] + // The shape of the character + access(all) let shape: EntryIdentifier + // The settings of the character + access(all) let settings: {CreatureSettings: Int64} + + view init( + attributes: Attributes, + defence: Defence, + potentiality: Potentiality, + features: [EntryIdentifier], + abilities: [EntryIdentifier], + items: [EntryIdentifier], + shape: EntryIdentifier, + settings: {CreatureSettings: Int64} + ) { + self.attributes = attributes + self.defence = defence + self.potentiality = potentiality + self.features = features + self.abilities = abilities + self.items = items + self.shape = shape + self.settings = settings + + + // check identifiers + assert(self.shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") + for featureIdentifier in features { + assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") + } + 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") + } + } + } + + // ---- Public Functions ---- + + access(all) + fun createLibrary(): @Library { + return <- create Library() } + access(all) view + fun borrowLibrary(_ address: Address): &Library? { + return getAccount(address).capabilities + .get<&Library>(self.libraryPublicPath) + .borrow() + } + + 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"))! + } } diff --git a/cadence/contracts/FGameMishalBattleField.cdc b/cadence/contracts/FGameMishalBattleField.cdc index 6719222..866b551 100644 --- a/cadence/contracts/FGameMishalBattleField.cdc +++ b/cadence/contracts/FGameMishalBattleField.cdc @@ -17,6 +17,11 @@ access(all) contract FGameMishalBattleField { access(all) entitlement SessionManage access(all) entitlement Creator + // The Pawn resource is refered to as the character in the game. + access(all) resource Pawn { + + } + // The Commander resource is refered to as the player in the game. access(all) resource Commander { } From 6cdea3544b78be7057d0be0ccf0d4ede0cc0ad1a Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sat, 17 May 2025 17:44:51 +0800 Subject: [PATCH 05/45] refactor: consolidate library entry management in FGameMishal contract; replace individual entry events and name-to-UID mappings with a unified structure for better scalability and maintainability --- cadence/contracts/FGameMishal.cdc | 337 +++++++++++++++++++++++------- 1 file changed, 256 insertions(+), 81 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 4186c2d..dbcf0a4 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -21,12 +21,8 @@ access(all) contract FGameMishal { // ----- Events ----- access(all) event LibrarySettingChanged(_ library: Address, key: UInt8, value: Int64) - access(all) event LibraryObjectAdded(_ library: Address, _ uuid: UInt64, _ name: String) - access(all) event LibraryItemAdded(_ library: Address, _ uuid: UInt64, _ name: String) - access(all) event LibraryAbilityAdded(_ library: Address, _ uuid: UInt64, _ name: String) - access(all) event LibraryShapeAdded(_ library: Address, _ uuid: UInt64, _ name: String) - access(all) event LibraryFeatureAdded(_ library: Address, _ uuid: UInt64, _ name: String) - access(all) event LibraryCreatureAdded(_ library: Address, _ uuid: UInt64, _ name: String) + access(all) event LibraryEntryAdded(_ library: Address, _ category: UInt8, _ uuid: UInt64, _ name: String, _ tags: [String]) + access(all) event LibraryEntryRemoved(_ library: Address, _ category: UInt8, _ uuid: UInt64) // ----- Contract Level Variables ----- @@ -89,12 +85,8 @@ access(all) contract FGameMishal { access(all) let shapes: @{UInt64: Shape} access(all) let features: @{UInt64: Feature} access(all) let creatures: @{UInt64: Creature} - access(self) let objectsNameToUID: {String: UInt64} - access(self) let itemsNameToUID: {String: UInt64} - access(self) let abilitiesNameToUID: {String: UInt64} - access(self) let shapesNameToUID: {String: UInt64} - access(self) let featuresNameToUID: {String: UInt64} - access(self) let creaturesNameToUID: {String: UInt64} + access(self) let nameToUID: {LibraryCategory: {String: UInt64}} + access(self) let tagToUIDs: {LibraryCategory: {String: [UInt64]}} init() { self.settings = { @@ -106,12 +98,23 @@ access(all) contract FGameMishal { self.shapes <- {} self.features <- {} self.creatures <- {} - self.objectsNameToUID = {} - self.itemsNameToUID = {} - self.abilitiesNameToUID = {} - self.shapesNameToUID = {} - self.featuresNameToUID = {} - self.creaturesNameToUID = {} + 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) @@ -124,85 +127,161 @@ access(all) contract FGameMishal { access(Editor) fun addObject(object: @Object) { pre { - self.objectsNameToUID[object.name] == nil: + self.borrowNameToUIDDictionary(LibraryCategory.OBJECT)[object.name] == nil: "Object name already exists" } let uuid = object.uuid let name = object.name - self.objectsNameToUID[name] = uuid + let tags = object.tags + self.setNameAndTags(LibraryCategory.OBJECT, uuid, name, tags) + self.objects[uuid] <-! object - emit LibraryObjectAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + emit LibraryEntryAdded( + self.owner?.address ?? panic("Owner not found"), + LibraryCategory.OBJECT.rawValue, + uuid, + name, + tags + ) } access(Editor) fun addItem(item: @Item) { pre { - self.itemsNameToUID[item.name] == nil: + self.borrowNameToUIDDictionary(LibraryCategory.ITEM)[item.name] == nil: "Item name already exists" } let uuid = item.uuid let name = item.name - self.itemsNameToUID[name] = uuid + let tags = item.tags + self.setNameAndTags(LibraryCategory.ITEM, uuid, name, tags) + self.items[uuid] <-! item - emit LibraryItemAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + emit LibraryEntryAdded( + self.owner?.address ?? panic("Owner not found"), + LibraryCategory.ITEM.rawValue, + uuid, + name, + tags + ) } access(Editor) fun addAbility(ability: @Ability) { pre { - self.abilitiesNameToUID[ability.name] == nil: + self.borrowNameToUIDDictionary(LibraryCategory.ABILITY)[ability.name] == nil: "Ability name already exists" } let uuid = ability.uuid let name = ability.name - self.abilitiesNameToUID[name] = uuid + let tags = ability.tags + self.setNameAndTags(LibraryCategory.ABILITY, uuid, name, tags) + self.abilities[uuid] <-! ability - emit LibraryAbilityAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + emit LibraryEntryAdded( + self.owner?.address ?? panic("Owner not found"), + LibraryCategory.ABILITY.rawValue, + uuid, + name, + tags + ) } access(Editor) fun addShape(shape: @Shape) { pre { - self.shapesNameToUID[shape.name] == nil: + self.borrowNameToUIDDictionary(LibraryCategory.SHAPE)[shape.name] == nil: "Shape name already exists" } let uuid = shape.uuid let name = shape.name - self.shapesNameToUID[name] = uuid + let tags = shape.tags + self.setNameAndTags(LibraryCategory.SHAPE, uuid, name, tags) + self.shapes[uuid] <-! shape - emit LibraryShapeAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + emit LibraryEntryAdded( + self.owner?.address ?? panic("Owner not found"), + LibraryCategory.SHAPE.rawValue, + uuid, + name, + tags + ) } access(Editor) fun addFeature(feature: @Feature) { pre { - self.featuresNameToUID[feature.name] == nil: + self.borrowNameToUIDDictionary(LibraryCategory.FEATURE)[feature.name] == nil: "Feature name already exists" } let uuid = feature.uuid let name = feature.name - self.featuresNameToUID[name] = uuid + let tags = feature.tags + self.setNameAndTags(LibraryCategory.FEATURE, uuid, name, tags) + self.features[uuid] <-! feature - emit LibraryFeatureAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + emit LibraryEntryAdded( + self.owner?.address ?? panic("Owner not found"), + LibraryCategory.FEATURE.rawValue, + uuid, + name, + tags + ) } access(Editor) fun addCreature(creature: @Creature) { pre { - self.creaturesNameToUID[creature.name] == nil: + self.borrowNameToUIDDictionary(LibraryCategory.CREATURE)[creature.name] == nil: "Creature name already exists" } let uuid = creature.uuid let name = creature.name - self.creaturesNameToUID[name] = uuid + let tags = creature.tags + self.setNameAndTags(LibraryCategory.CREATURE, uuid, name, tags) + self.creatures[uuid] <-! creature - emit LibraryCreatureAdded(self.owner?.address ?? panic("Owner not found"), uuid, name) + 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 -------- @@ -214,7 +293,7 @@ access(all) contract FGameMishal { access(all) view fun borrowObjectByName(_ name: String): &Object? { - if let uuid = self.objectsNameToUID[name] { + if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.OBJECT)[name] { return self.borrowObject(uuid) } return nil @@ -227,7 +306,7 @@ access(all) contract FGameMishal { access(all) view fun borrowItemByName(_ name: String): &Item? { - if let uuid = self.itemsNameToUID[name] { + if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.ITEM)[name] { return self.borrowItem(uuid) } return nil @@ -240,7 +319,7 @@ access(all) contract FGameMishal { access(all) view fun borrowAbilityByName(_ name: String): &Ability? { - if let uuid = self.abilitiesNameToUID[name] { + if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.ABILITY)[name] { return self.borrowAbility(uuid) } return nil @@ -253,7 +332,7 @@ access(all) contract FGameMishal { access(all) view fun borrowShapeByName(_ name: String): &Shape? { - if let uuid = self.shapesNameToUID[name] { + if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.SHAPE)[name] { return self.borrowShape(uuid) } return nil @@ -266,7 +345,7 @@ access(all) contract FGameMishal { access(all) view fun borrowFeatureByName(_ name: String): &Feature? { - if let uuid = self.featuresNameToUID[name] { + if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.FEATURE)[name] { return self.borrowFeature(uuid) } return nil @@ -279,11 +358,89 @@ access(all) contract FGameMishal { access(all) view fun borrowCreatureByName(_ name: String): &Creature? { - if let uuid = self.creaturesNameToUID[name] { + if let uuid = self.borrowNameToUIDDictionary(LibraryCategory.CREATURE)[name] { return self.borrowCreature(uuid) } return nil } + + 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) view + fun getTagUIDs(_ category: LibraryCategory, _ tag: String): [UInt64] { + let tagToUIDs = self.borrowTagToUIDsDictionary(category) + if let uids = tagToUIDs[tag] { + return *uids + } + return [] + } + + // -------- Private Functions -------- + + 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 ------------ @@ -412,27 +569,27 @@ access(all) contract FGameMishal { access(all) resource interface Nameable { access(all) let name: String - } - - // The PotentialityCarrier resource interface is used to get the potentiality of the entry. - access(all) resource interface PotentialityCarrier { - access(all) view fun getInitialPotentiality(): Int64 { - return self.borrowPotentiality()?.initial ?? 0 - } + access(all) let tags: [String] - access(all) view fun getCurrentPotentiality(): Int64 { - return self.borrowPotentiality()?.current ?? 0 + access(all) view + fun hasAnyTag(): Bool { + return self.tags.length > 0 } - access(all) view fun getUsedPotentiality(): Int64 { - return self.borrowPotentiality()?.used ?? 0 + access(all) view + fun withTag(_ tag: String): Bool { + return self.tags.contains(tag) } - - access(all) view fun borrowPotentiality(): &Potentiality? } // 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 } @@ -444,12 +601,16 @@ access(all) contract FGameMishal { access(all) view fun getAttrSpir(): Int64 { return self.borrowAttributes()?.spirit ?? 0 } - - access(all) view fun borrowAttributes(): &Attributes? } // 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 } @@ -461,24 +622,39 @@ access(all) contract FGameMishal { access(all) view fun getDefRes(): Int64 { return self.borrowDefence()?.resistance ?? 0 } - - access(all) view fun borrowDefence(): &Defence? } - // The StatusCarrier resource interface is used to get the status of the entry. - access(all) resource interface StatusCarrier: AttributeCarrier, DefenceCarrier { - // Main attributes of the character - access(all) let attributes: Attributes - // The defence of the character - access(all) let defence: Defence + // 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 borrowAttributes(): &Attributes? { - return &self.attributes as &Attributes + access(all) view fun hasPotentiality(): Bool { + return self.borrowPotentiality() != nil } - access(all) view fun borrowDefence(): &Defence? { - return &self.defence as &Defence + access(all) view fun getInitialPotentiality(): Int64 { + return self.borrowPotentiality()?.initial ?? 0 + } + + access(all) view fun getCurrentPotentiality(): Int64 { + return self.borrowPotentiality()?.current ?? 0 } + + access(all) view fun getUsedPotentiality(): Int64 { + return self.borrowPotentiality()?.used ?? 0 + } + } + + // The StatusCarrier resource interface is used to get the status of the entry. + access(all) resource interface OptionalStatusCarrier: AttributeCarrier, DefenceCarrier, PotentialityCarrier { + // TODO + } + + access(all) resource interface LiveStatusCarrier: AttributeCarrier, DefenceCarrier, PotentialityCarrier { + // TODO + access(all) let attributes: Attributes + access(all) let defence: Defence + access(all) let potentiality: Potentiality } access(all) resource interface ValueCarrier { @@ -524,6 +700,7 @@ access(all) contract FGameMishal { 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 @@ -531,10 +708,12 @@ access(all) contract FGameMishal { view init( name: String, + tags: [String], defence: Defence, value: UFix64? ) { self.name = name + self.tags = tags self.defence = defence self.value = value } @@ -544,31 +723,36 @@ access(all) contract FGameMishal { } } - access(all) resource Item: StatusCarrier, ValueCarrier, EffectsCarrier, Nameable { + 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] + 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, effects: [String], - slotsOccupied: [EquipSlot], + slotsOccupied: {EquipSlot: UInt8}, slotsProvided: {EquipSlot: UInt8}, ) { self.name = name + self.tags = tags self.value = value self.attributes = attributes self.defence = defence @@ -953,15 +1137,6 @@ access(all) contract FGameMishal { } } - access(all) resource interface LiveStatusCarrier: StatusCarrier, PotentialityCarrier { - access(all) let potentiality: Potentiality - - access(all) view - fun borrowPotentiality(): &Potentiality? { - return &self.potentiality as &Potentiality - } - } - access(all) resource interface CreatureInterface: LiveStatusCarrier, FeaturesCarrier, AbilitiesCarrier, ItemsCarrier, ShapeOverrides { // Main attributes of the character access(all) let attributes: Attributes From f003396082d28c5443cbbe79239f7f8ae79f8c24 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sat, 17 May 2025 20:45:34 +0800 Subject: [PATCH 06/45] feat: introduce UnitStatus and related interfaces in FGameMishal contract; enhance unit management with new attributes, defence, and potentiality handling, and emit UnitStatusApplied event for status updates --- cadence/contracts/FGameMishal.cdc | 480 ++++++++++++++++++++++-------- 1 file changed, 348 insertions(+), 132 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index dbcf0a4..73b2cf3 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -24,6 +24,8 @@ access(all) contract FGameMishal { 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) + // ----- Contract Level Variables ----- // The counter variable for the library items @@ -450,31 +452,22 @@ access(all) contract FGameMishal { } access(all) struct Potentiality: Copyable { - access(all) let initial: Int64 - access(all) var current: Int64 - access(all) var used: Int64 + access(all) var initial: Int64 - view init(initial: Int64) { + view init( + initial: Int64, + ) { self.initial = initial - self.current = initial - self.used = 0 } access(all) fun copy(): {Copyable} { return self } access(contract) - fun add(amount: Int64) { - self.current = self.current + amount - } - - access(contract) - fun use(amount: Int64) { - pre { - self.current >= amount: - "Not enough potentiality" + fun add(_ amount: Int64) { + post { + self.initial >= 0: "Initial potentiality cannot be negative" } - self.current = self.current - amount - self.used = self.used + amount + self.initial = self.initial + amount } } @@ -493,32 +486,32 @@ access(all) contract FGameMishal { access(all) fun copy(): {Copyable} { return self } access(contract) - fun setStrength(strength: Int64) { + fun setStrength(_ strength: Int64) { self.strength = strength } access(contract) - fun addStrength(strength: Int64) { + fun addStrength(_ strength: Int64) { self.strength = self.strength + strength } access(contract) - fun setVitality(vitality: Int64) { + fun setVitality(_ vitality: Int64) { self.vitality = vitality } access(contract) - fun addVitality(vitality: Int64) { + fun addVitality(_ vitality: Int64) { self.vitality = self.vitality + vitality } access(contract) - fun setSpirit(spirit: Int64) { + fun setSpirit(_ spirit: Int64) { self.spirit = spirit } access(contract) - fun addSpirit(spirit: Int64) { + fun addSpirit(_ spirit: Int64) { self.spirit = self.spirit + spirit } } @@ -537,36 +530,85 @@ access(all) contract FGameMishal { access(all) fun copy(): {Copyable} { return self } access(contract) - fun setPhysical(physical: Int64) { + fun setPhysical(_ physical: Int64) { self.physical = physical } access(contract) - fun addPhysical(physical: Int64) { + fun addPhysical(_ physical: Int64) { self.physical = self.physical + physical } access(contract) - fun setEndurance(endurance: Int64) { + fun setEndurance(_ endurance: Int64) { self.endurance = endurance } access(contract) - fun addEndurance(endurance: Int64) { + fun addEndurance(_ endurance: Int64) { self.endurance = self.endurance + endurance } access(contract) - fun setResistance(resistance: Int64) { + fun setResistance(_ resistance: Int64) { self.resistance = resistance } access(contract) - fun addResistance(resistance: Int64) { + fun addResistance(_ resistance: Int64) { self.resistance = self.resistance + resistance } } + // 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 + + view init( + attributes: Attributes, + defence: Defence, + potentiality: Potentiality + ) { + self.attributes = attributes + self.defence = defence + self.potentiality = potentiality + } + + 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(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] @@ -582,6 +624,47 @@ access(all) contract FGameMishal { } } + 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(Manage) + fun setValue(value: UFix64) { + self.value = value + } + + access(Manage) + fun addValue(value: UFix64) { + 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(Manage) + fun addEffect(effect: String) { + self.effects.append(effect) + } + + access(Manage) + 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? @@ -635,66 +718,87 @@ access(all) contract FGameMishal { access(all) view fun getInitialPotentiality(): Int64 { return self.borrowPotentiality()?.initial ?? 0 } - - access(all) view fun getCurrentPotentiality(): Int64 { - return self.borrowPotentiality()?.current ?? 0 - } - - access(all) view fun getUsedPotentiality(): Int64 { - return self.borrowPotentiality()?.used ?? 0 - } } - // The StatusCarrier resource interface is used to get the status of the entry. + // The OptionalStatusCarrier resource interface is used to get the optional status of the entry. access(all) resource interface OptionalStatusCarrier: AttributeCarrier, DefenceCarrier, PotentialityCarrier { - // TODO - } + access(all) let attributes: Attributes? + access(all) let defence: Defence? + access(all) let potentiality: Potentiality? - access(all) resource interface LiveStatusCarrier: AttributeCarrier, DefenceCarrier, PotentialityCarrier { - // TODO - 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 + } } - access(all) resource interface ValueCarrier { - // The value of the item - access(all) var value: UFix64? + // 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 hasValue(): Bool { - return self.value != nil + access(all) view fun borrowAttributes(): &Attributes? { + return self.borrowStatus().borrowAttributes() } - access(Manage) - fun setValue(value: UFix64) { - self.value = value + access(all) view fun borrowDefence(): &Defence? { + return self.borrowStatus().borrowDefence() } - access(Manage) - fun addValue(value: UFix64) { - self.value = (self.value ?? 0.0) + value + access(all) view fun borrowPotentiality(): &Potentiality? { + return self.borrowStatus().borrowPotentiality() } } - access(all) resource interface EffectsCarrier { - access(all) let effects: [String] + access(all) resource interface ComposableUnitStatusCarrier: LiveUnitStatusCarrier { + access(all) let status: UnitStatus - access(all) view - fun hasEffects(): Bool { - return self.effects.length > 0 + access(all) view fun borrowStatus(): &UnitStatus { + return &self.status } - access(Manage) - fun addEffect(effect: String) { - self.effects.append(effect) - } + access(all) fun borrowAttributesElements(): [&Attributes] + access(all) fun borrowDefenceElements(): [&Defence] + access(all) fun borrowPotentialityElements(): [&Potentiality] - access(Manage) - fun removeEffect(effect: String) { - if let index = self.effects.firstIndex(of: effect) { - let _ = self.effects.remove(at: index) + access(contract) + fun applyStatus() { + // 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.addStrength(attribute.strength) + newAttributes.addVitality(attribute.vitality) + newAttributes.addSpirit(attribute.spirit) } + self.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.addPhysical(one.physical) + newDefence.addEndurance(one.endurance) + newDefence.addResistance(one.resistance) + } + self.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) + } + self.status.setPotentiality(newPotentiality) + + // Emit the event + emit UnitStatusApplied(self.uuid, self.status.attributes, self.status.defence, self.status.potentiality) } } @@ -729,11 +833,11 @@ access(all) contract FGameMishal { // The value of the item access(all) var value: UFix64? // Main attributes of the character - access(all) let attributes: Attributes + access(all) let attributes: Attributes? // The defence of the character - access(all) let defence: Defence + access(all) let defence: Defence? // The potentiality of the item - access(all) let potentiality: Potentiality + access(all) let potentiality: Potentiality? // The effects of the item access(all) let effects: [String] // The slots occupied by the item @@ -745,8 +849,9 @@ access(all) contract FGameMishal { name: String, tags: [String], value: UFix64?, - attributes: Attributes, - defence: Defence, + attributes: Attributes?, + defence: Defence?, + potentiality: Potentiality?, effects: [String], slotsOccupied: {EquipSlot: UInt8}, slotsProvided: {EquipSlot: UInt8}, @@ -756,6 +861,7 @@ access(all) contract FGameMishal { self.value = value self.attributes = attributes self.defence = defence + self.potentiality = potentiality self.effects = effects self.slotsOccupied = slotsOccupied self.slotsProvided = slotsProvided @@ -764,6 +870,7 @@ access(all) contract FGameMishal { access(all) resource Ability: AttributeCarrier, EffectsCarrier, Nameable { access(all) let name: String + access(all) let tags: [String] access(all) let level: UInt64 access(all) let occupy: Attributes access(all) let effects: [String] @@ -771,10 +878,12 @@ access(all) contract FGameMishal { view init( level: UInt64, name: String, + tags: [String], occupy: Attributes, effects: [String] ) { self.name = name + self.tags = tags self.level = level self.occupy = occupy self.effects = effects @@ -802,11 +911,13 @@ access(all) contract FGameMishal { access(all) resource Shape: CreatureSettingsCarrier, 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, @@ -814,6 +925,7 @@ access(all) contract FGameMishal { slotsAvailable: {EquipSlot: UInt8 } ) { self.name = name + self.tags = tags self.settings = {} self.slotsAvailable = slotsAvailable @@ -990,11 +1102,11 @@ access(all) contract FGameMishal { } access(all) resource interface AbilitiesCarrier { - access(all) let abilities: [EntryIdentifier] + access(all) view fun borrowAbilityIdentifiers(): &[EntryIdentifier] access(all) fun borrowAbilities(): [&Ability] { - return self.abilities + return self.borrowAbilityIdentifiers() .map(view fun (_ x: EntryIdentifier): &Ability? { return x.borrowAbility() }) @@ -1008,16 +1120,16 @@ access(all) contract FGameMishal { access(all) view fun hasAbilities(): Bool { - return self.abilities.length > 0 + return self.borrowAbilityIdentifiers().length > 0 } } access(all) resource interface ItemsCarrier { - access(all) let items: [EntryIdentifier] + access(all) view fun borrowItemIdentifiers(): &[EntryIdentifier] access(all) fun borrowItems(): [&Item] { - return self.items + return self.borrowItemIdentifiers() .map(view fun (_ x: EntryIdentifier): &Item? { return x.borrowItem() }) @@ -1031,12 +1143,11 @@ access(all) contract FGameMishal { access(all) view fun hasItems(): Bool { - return self.items.length > 0 + return self.borrowItemIdentifiers().length > 0 } } - access(all) resource interface ShapeCarrier: ShapeOverrides, Nameable { - access(all) let name: String + access(all) resource interface ShapeCarrier: ShapeOverrides { access(all) let shape: EntryIdentifier? access(all) view @@ -1053,9 +1164,25 @@ access(all) contract FGameMishal { } } + access(all) resource interface UnitStaticBasicCapabiltiesCarrier: AbilitiesCarrier, ItemsCarrier { + access(all) let abilities: [EntryIdentifier] + access(all) let items: [EntryIdentifier] + + access(all) view + fun borrowAbilityIdentifiers(): &[EntryIdentifier] { + return &self.abilities + } + + access(all) view + fun borrowItemIdentifiers(): &[EntryIdentifier] { + return &self.items + } + } + // The Feature resource is used to define the features of the entry. - access(all) resource Feature: Nameable, AbilitiesCarrier, ItemsCarrier, ShapeCarrier, EffectsCarrier { + access(all) resource Feature: Nameable, OptionalStatusCarrier, UnitStaticBasicCapabiltiesCarrier, ShapeCarrier, EffectsCarrier { 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? @@ -1067,6 +1194,7 @@ access(all) contract FGameMishal { view init( name: String, + tags: [String], attributes: Attributes?, defence: Defence?, potentiality: Potentiality?, @@ -1077,6 +1205,7 @@ access(all) contract FGameMishal { settings: {CreatureSettings: Int64} ) { self.name = name + self.tags = tags self.attributes = attributes self.defence = defence self.potentiality = potentiality @@ -1097,29 +1226,14 @@ access(all) contract FGameMishal { assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid") } } - - access(all) view - fun hasAttributes(): Bool { - return self.attributes != nil - } - - access(all) view - fun hasDefence(): Bool { - return self.defence != nil - } - - access(all) view - fun hasPotentiality(): Bool { - return self.potentiality != nil - } } access(all) resource interface FeaturesCarrier { - access(all) let features: [EntryIdentifier] + access(all) view fun borrowFeatureIdentifiers(): &[EntryIdentifier] access(all) fun borrowFeatures(): [&Feature] { - return self.features + return self.borrowFeatureIdentifiers() .map(view fun (_ x: EntryIdentifier): &Feature? { return x.borrowFeature() }) @@ -1133,42 +1247,126 @@ access(all) contract FGameMishal { access(all) view fun hasFeatures(): Bool { - return self.features.length > 0 + return self.borrowFeatureIdentifiers().length > 0 } } - access(all) resource interface CreatureInterface: LiveStatusCarrier, FeaturesCarrier, AbilitiesCarrier, ItemsCarrier, ShapeOverrides { - // Main attributes of the character - access(all) let attributes: Attributes - // The defence of the character - access(all) let defence: Defence - // The potentiality of the character - access(all) let potentiality: Potentiality - // The features of the character + access(all) resource interface UnitStaticCapabiltiesCarrier: UnitStaticBasicCapabiltiesCarrier, FeaturesCarrier { access(all) let features: [EntryIdentifier] - // The abilities of the character - access(all) let abilities: [EntryIdentifier] - // The items of the character - access(all) let items: [EntryIdentifier] + + access(all) view + fun borrowFeatureIdentifiers(): &[EntryIdentifier] { + return &self.features + } + } + + access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, ItemsCarrier, FeaturesCarrier, ShapeOverrides { // The shape of the character access(all) let shape: EntryIdentifier - // The settings of the character - access(all) let settings: {CreatureSettings: Int64} + // The base attributes of the character + access(all) let baseAttributes: Attributes + // The base defence of the character + access(all) let baseDefence: Defence + // The base potentiality of the character + access(all) let basePotentiality: Potentiality access(all) view fun borrowShape(): &Shape? { return self.shape.borrowShape() } + + access(all) + fun borrowAttributesElements(): [&Attributes] { + let ret: [&Attributes] = [&self.baseAttributes] + + // 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.borrowItems() + for item in items { + if item.hasAttributes() { + if let attributes = item.borrowAttributes() { + ret.append(attributes) + } + } + } + return ret + } + + access(all) + fun borrowDefenceElements(): [&Defence] { + let ret: [&Defence] = [&self.baseDefence] + + // From Features + let features = self.borrowFeatures() + for feature in features { + if feature.hasDefence() { + if let defence = feature.borrowDefence() { + ret.append(defence) + } + } + } + + // From Items + let items = self.borrowItems() + for item in items { + if item.hasDefence() { + if let defence = item.borrowDefence() { + ret.append(defence) + } + } + } + return ret + } + + access(all) + fun borrowPotentialityElements(): [&Potentiality] { + let ret: [&Potentiality] = [&self.basePotentiality] + + // 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.borrowItems() + for item in items { + if item.hasPotentiality() { + if let potentiality = item.borrowPotentiality() { + ret.append(potentiality) + } + } + } + return ret + } } - access(all) resource Creature: Nameable, CreatureInterface { + access(all) resource Creature: Nameable, CreatureInterface, UnitStaticCapabiltiesCarrier { access(all) let name: String - // Main attributes of the character - access(all) let attributes: Attributes - // The defence of the character - access(all) let defence: Defence - // The potentiality of the character - access(all) let potentiality: Potentiality + access(all) let tags: [String] // The features of the character access(all) let features: [EntryIdentifier] // The abilities of the character @@ -1179,9 +1377,18 @@ access(all) contract FGameMishal { access(all) let shape: EntryIdentifier // The settings of the character access(all) let settings: {CreatureSettings: Int64} + // Main attributes of the character + access(all) let baseAttributes: Attributes + // The base defence of the character + access(all) let baseDefence: Defence + // The potentiality of the character + access(all) let basePotentiality: Potentiality + // The merged status of the character + access(all) let status: UnitStatus - view init( + init( name: String, + tags: [String], attributes: Attributes, defence: Defence, potentiality: Potentiality, @@ -1192,15 +1399,21 @@ access(all) contract FGameMishal { settings: {CreatureSettings: Int64} ) { self.name = name - self.attributes = attributes - self.defence = defence - self.potentiality = potentiality + self.tags = tags + self.baseAttributes = attributes + self.baseDefence = defence + self.basePotentiality = potentiality self.features = features self.abilities = abilities self.items = items self.shape = shape self.settings = settings - + // Initialize the status + self.status = UnitStatus( + attributes: self.baseAttributes, + defence: self.baseDefence, + potentiality: self.basePotentiality + ) // check identifiers assert(self.shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") @@ -1213,6 +1426,9 @@ access(all) contract FGameMishal { for itemIdentifier in items { assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid") } + + // apply the status + self.applyStatus() } } @@ -1227,11 +1443,11 @@ access(all) contract FGameMishal { // The Pawn resource is refered to as the character in the game. access(all) resource Pawn: CreatureInterface { // Main attributes of the character - access(all) let attributes: Attributes - // The defence of the character - access(all) let defence: Defence + access(all) let baseAttributes: Attributes + // The base defence of the character + access(all) let baseDefence: Defence // The potentiality of the character - access(all) let potentiality: Potentiality + access(all) let basePotentiality: Potentiality // The features of the character access(all) let features: [EntryIdentifier] // The abilities of the character From e477ed0bb3ebb631d70f93c24423bdc5f15b14d9 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sat, 17 May 2025 23:12:51 +0800 Subject: [PATCH 07/45] feat: enhance FGameMishal contract with new fungible entry management; introduce EntryCollection and FungibleEntry resources, update event emissions for deposits and withdrawals, and improve item handling with amounts --- cadence/contracts/FGameMishal.cdc | 509 ++++++++++++++++++++++-------- 1 file changed, 375 insertions(+), 134 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 73b2cf3..217a5f7 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -8,6 +8,7 @@ The basic elements of a Mishal Game will be defined here. */ import "Burner" +import "FungibleToken" // Fixes Imports import "Fixes" import "FixesHeartbeat" @@ -26,6 +27,9 @@ access(all) contract FGameMishal { 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) + // ----- Contract Level Variables ----- // The counter variable for the library items @@ -38,6 +42,8 @@ access(all) contract FGameMishal { access(all) enum LibrarySettings: UInt8 { access(all) case INIT_POTENTIALITY + access(all) case INIT_ATTRIBUTE_VALUE + access(all) case INIT_DEFENCE_VALUE } access(all) enum LibraryCategory: UInt8 { @@ -80,19 +86,22 @@ access(all) contract FGameMishal { // The Public Library resource is used to store the settings of Mishal Game. access(all) resource Library { - access(all) let settings: {LibrarySettings: Int64} - access(all) let objects: @{UInt64: Object} - access(all) let items: @{UInt64: Item} - access(all) let abilities: @{UInt64: Ability} - access(all) let shapes: @{UInt64: Shape} - access(all) let features: @{UInt64: Feature} - access(all) let creatures: @{UInt64: Creature} + 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.INIT_POTENTIALITY: 36 + LibrarySettings.INIT_POTENTIALITY: 36, + LibrarySettings.INIT_ATTRIBUTE_VALUE: 3, + LibrarySettings.INIT_DEFENCE_VALUE: 0 } self.objects <- {} self.items <- {} @@ -937,11 +946,16 @@ access(all) contract FGameMishal { } // The ShapeCarrier resource interface is used to get the shape of the creature. - access(all) resource interface ShapeOverrides: CreatureSettingsCarrier { + access(all) resource interface ShapeCarrier: CreatureSettingsCarrier { access(all) let settings: {CreatureSettings: Int64} access(all) view fun borrowShape(): &Shape? + access(all) view + fun hasShape(): Bool { + return self.borrowShape() != nil + } + access(all) view fun getGender(): Int64? { if let gender = self.settings[CreatureSettings.GENDER] { @@ -1024,6 +1038,11 @@ access(all) contract FGameMishal { 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 verify(_ type: LibraryCategory): Bool { if type != self.category { @@ -1101,12 +1120,178 @@ access(all) contract FGameMishal { } } + // 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 EntryCollection resource is used to store the entries. + access(all) resource EntryCollection { + 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(EntryIdentifier(library: ref.identifier.library, category: ref.identifier.category, id: ref.identifier.id)) + } + } + return ret + } + + access(all) + fun borrowEnties(_ 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(all) + 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 self.isNonFungible(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(all) + 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 self.isNonFungible(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(self) view + fun isNonFungible(_ category: LibraryCategory): Bool { + switch category { + case LibraryCategory.OBJECT: + return false + case LibraryCategory.ITEM: + return false + default: + return true + } + } + } + access(all) resource interface AbilitiesCarrier { - access(all) view fun borrowAbilityIdentifiers(): &[EntryIdentifier] + access(all) view fun getAbilitiesLength(): Int + access(all) fun getAbilityIdentifiers(): [EntryIdentifier] access(all) fun borrowAbilities(): [&Ability] { - return self.borrowAbilityIdentifiers() + return self.getAbilityIdentifiers() .map(view fun (_ x: EntryIdentifier): &Ability? { return x.borrowAbility() }) @@ -1120,79 +1305,81 @@ access(all) contract FGameMishal { access(all) view fun hasAbilities(): Bool { - return self.borrowAbilityIdentifiers().length > 0 + return self.getAbilitiesLength() > 0 } } access(all) resource interface ItemsCarrier { - access(all) view fun borrowItemIdentifiers(): &[EntryIdentifier] + access(all) view fun getItemsLength(): Int + access(all) fun borrowItemEntries(): [&FungibleEntry] access(all) fun borrowItems(): [&Item] { - return self.borrowItemIdentifiers() - .map(view fun (_ x: EntryIdentifier): &Item? { - return x.borrowItem() - }) - .filter(view fun (_ x: &Item?): Bool { - return x != nil - }) - .map(view fun (_ x: &Item?): &Item { - return x! - }) + 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.borrowItemIdentifiers().length > 0 + return self.getItemsLength() > 0 } } - access(all) resource interface ShapeCarrier: ShapeOverrides { - access(all) let shape: EntryIdentifier? + access(all) resource interface UnitStaticBasicCapabiltiesCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { + access(all) let collection: @EntryCollection - access(all) view - fun borrowShape(): &Shape? { - if let shape = self.shape { - return shape.borrowShape() - } - return nil + access(all) view fun getAbilitiesLength(): Int { + return self.collection.getLengthByCategory(LibraryCategory.ABILITY) } - access(all) view - fun hasShape(): Bool { - return self.shape != nil + access(all) + fun getAbilityIdentifiers(): [EntryIdentifier] { + return self.collection.getEntryIdentifiers(LibraryCategory.ABILITY) } - } - access(all) resource interface UnitStaticBasicCapabiltiesCarrier: AbilitiesCarrier, ItemsCarrier { - access(all) let abilities: [EntryIdentifier] - access(all) let items: [EntryIdentifier] + access(all) view fun getItemsLength(): Int { + return self.collection.getLengthByCategory(LibraryCategory.ITEM) + } - access(all) view - fun borrowAbilityIdentifiers(): &[EntryIdentifier] { - return &self.abilities + access(all) + fun borrowItemEntries(): [&FungibleEntry] { + return self.collection.borrowEnties(LibraryCategory.ITEM) } - access(all) view - fun borrowItemIdentifiers(): &[EntryIdentifier] { - return &self.items + access(all) view fun borrowShape(): &Shape? { + let shape = self.collection.getKeysByCategory(LibraryCategory.SHAPE) + if shape.length > 0 { + if let entry = self.collection.borrowEntryByID(shape[0]) { + return entry.identifier.borrowShape() + } + } + return nil } } // The Feature resource is used to define the features of the entry. - access(all) resource Feature: Nameable, OptionalStatusCarrier, UnitStaticBasicCapabiltiesCarrier, ShapeCarrier, EffectsCarrier { + access(all) resource Feature: Nameable, OptionalStatusCarrier, UnitStaticBasicCapabiltiesCarrier, EffectsCarrier { 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 abilities: [EntryIdentifier] - access(all) let items: [EntryIdentifier] - access(all) let shape: EntryIdentifier? access(all) let settings: {CreatureSettings: Int64} - view init( + init( name: String, tags: [String], attributes: Attributes?, @@ -1201,39 +1388,54 @@ access(all) contract FGameMishal { 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.shape = shape - self.abilities = abilities - self.items = items self.effects = effects self.settings = settings + self.collection <- create EntryCollection() + + // Set the entries // check identifiers - if self.shape != nil { - assert(self.shape!.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") + if shape != nil { + assert(shape!.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") + self.collection.deposit(entry: <-create FungibleEntry(identifier: shape!, amount: 1.0)) } for abilityIdentifier in abilities { - assert(abilityIdentifier.verify(LibraryCategory.ABILITY), message: "Ability identifier is invalid") + self.collection.deposit(entry: <-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0)) } - for itemIdentifier in items { - assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid") + 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.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) + } } } } access(all) resource interface FeaturesCarrier { - access(all) view fun borrowFeatureIdentifiers(): &[EntryIdentifier] + access(all) view fun getFeaturesLength(): Int + access(all) fun getFeatureIdentifiers(): [EntryIdentifier] access(all) fun borrowFeatures(): [&Feature] { - return self.borrowFeatureIdentifiers() + return self.getFeatureIdentifiers() .map(view fun (_ x: EntryIdentifier): &Feature? { return x.borrowFeature() }) @@ -1247,37 +1449,43 @@ access(all) contract FGameMishal { access(all) view fun hasFeatures(): Bool { - return self.borrowFeatureIdentifiers().length > 0 + return self.getFeaturesLength() > 0 } } access(all) resource interface UnitStaticCapabiltiesCarrier: UnitStaticBasicCapabiltiesCarrier, FeaturesCarrier { - access(all) let features: [EntryIdentifier] + access(all) view fun getFeaturesLength(): Int { + return self.collection.getLengthByCategory(LibraryCategory.FEATURE) + } - access(all) view - fun borrowFeatureIdentifiers(): &[EntryIdentifier] { - return &self.features + access(all) + fun getFeatureIdentifiers(): [EntryIdentifier] { + return self.collection.getEntryIdentifiers(LibraryCategory.FEATURE) } } - access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, ItemsCarrier, FeaturesCarrier, ShapeOverrides { - // The shape of the character - access(all) let shape: EntryIdentifier - // The base attributes of the character - access(all) let baseAttributes: Attributes - // The base defence of the character - access(all) let baseDefence: Defence - // The base potentiality of the character - access(all) let basePotentiality: Potentiality + access(all) resource interface BioCarrier { + access(all) let bioPrompts: [String] access(all) view - fun borrowShape(): &Shape? { - return self.shape.borrowShape() + fun borrowBioPrompts(): &[String] { + return &self.bioPrompts } + access(Manage) + fun addBioPrompt(_ prompt: String) { + self.bioPrompts.append(prompt) + } + } + + access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, ItemsCarrier, FeaturesCarrier, ShapeCarrier { + access(all) view fun borrowSelfAttributes(): &Attributes + access(all) view fun borrowSelfDefence(): &Defence + access(all) view fun borrowSelfPotentiality(): &Potentiality + access(all) fun borrowAttributesElements(): [&Attributes] { - let ret: [&Attributes] = [&self.baseAttributes] + let ret: [&Attributes] = [self.borrowSelfAttributes()] // From Features let features = self.borrowFeatures() @@ -1313,7 +1521,7 @@ access(all) contract FGameMishal { access(all) fun borrowDefenceElements(): [&Defence] { - let ret: [&Defence] = [&self.baseDefence] + let ret: [&Defence] = [self.borrowSelfDefence()] // From Features let features = self.borrowFeatures() @@ -1339,7 +1547,7 @@ access(all) contract FGameMishal { access(all) fun borrowPotentialityElements(): [&Potentiality] { - let ret: [&Potentiality] = [&self.basePotentiality] + let ret: [&Potentiality] = [self.borrowSelfPotentiality()] // From Features let features = self.borrowFeatures() @@ -1364,17 +1572,10 @@ access(all) contract FGameMishal { } } - access(all) resource Creature: Nameable, CreatureInterface, UnitStaticCapabiltiesCarrier { + access(all) resource Creature: Nameable, CreatureInterface, UnitStaticCapabiltiesCarrier, BioCarrier { access(all) let name: String access(all) let tags: [String] - // The features of the character - access(all) let features: [EntryIdentifier] - // The abilities of the character - access(all) let abilities: [EntryIdentifier] - // The items of the character - access(all) let items: [EntryIdentifier] - // The shape of the character - access(all) let shape: EntryIdentifier + access(all) let collection: @EntryCollection // The settings of the character access(all) let settings: {CreatureSettings: Int64} // Main attributes of the character @@ -1385,6 +1586,8 @@ access(all) contract FGameMishal { access(all) let basePotentiality: Potentiality // The merged status of the character access(all) let status: UnitStatus + // The bio prompts of the character + access(all) let bioPrompts: [String] init( name: String, @@ -1395,101 +1598,139 @@ access(all) contract FGameMishal { features: [EntryIdentifier], abilities: [EntryIdentifier], items: [EntryIdentifier], + itemAmounts: {String: UFix64}, shape: EntryIdentifier, - settings: {CreatureSettings: Int64} + settings: {CreatureSettings: Int64}, + bioPrompts: [String] ) { self.name = name self.tags = tags + self.settings = settings + self.bioPrompts = bioPrompts self.baseAttributes = attributes self.baseDefence = defence self.basePotentiality = potentiality - self.features = features - self.abilities = abilities - self.items = items - self.shape = shape - self.settings = settings // Initialize the status self.status = UnitStatus( attributes: self.baseAttributes, defence: self.baseDefence, potentiality: self.basePotentiality ) + self.collection <- create EntryCollection() + + assert(shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") + self.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) - // check identifiers - assert(self.shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") for featureIdentifier in features { assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") + self.collection.deposit(entry: <-create FungibleEntry(identifier: featureIdentifier, amount: 1.0)) } + for abilityIdentifier in abilities { assert(abilityIdentifier.verify(LibraryCategory.ABILITY), message: "Ability identifier is invalid") + self.collection.deposit(entry: <-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0)) } - for itemIdentifier in items { - assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid") + + 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.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) + } } // apply the status self.applyStatus() } + + access(all) view + fun borrowSelfAttributes(): &Attributes { + return &self.baseAttributes + } + + access(all) view + fun borrowSelfDefence(): &Defence { + return &self.baseDefence + } + + access(all) view + fun borrowSelfPotentiality(): &Potentiality { + return &self.basePotentiality + } } // ------------ Player ------------ - // The Clonable resource inteface is used to clone the entry. - access(all) resource interface Clonable { - access(all) let identifier: EntryIdentifier - access(Manage) fun clone(): @{Clonable} + access(all) resource CultivableProperty { + access(contract) let library: Address + + access(all) let attributes: Attributes + access(all) let defence: Defence + access(all) let potentiality: Potentiality + + view init( + _ library: Address + ) { + self.library = library + let lib = FGameMishal.borrowLibrary(library) ?? panic("Library not found") + let initPtt = lib.settings[LibrarySettings.INIT_POTENTIALITY] ?? 36 + let initDef = lib.settings[LibrarySettings.INIT_DEFENCE_VALUE] ?? 0 + let initAttr = lib.settings[LibrarySettings.INIT_ATTRIBUTE_VALUE] ?? 3 + + self.attributes = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) + self.defence = Defence(physical: initDef, endurance: initDef, resistance: initDef) + self.potentiality = Potentiality(initial: initPtt) + } } // The Pawn resource is refered to as the character in the game. - access(all) resource Pawn: CreatureInterface { - // Main attributes of the character - access(all) let baseAttributes: Attributes - // The base defence of the character - access(all) let baseDefence: Defence - // The potentiality of the character - access(all) let basePotentiality: Potentiality + access(all) resource Pawn: CreatureInterface, BioCarrier { + // The shape of the character + access(all) let shape: EntryIdentifier // The features of the character access(all) let features: [EntryIdentifier] - // The abilities of the character - access(all) let abilities: [EntryIdentifier] + // The merged status of the character + access(all) let status: UnitStatus // The items of the character - access(all) let items: [EntryIdentifier] - // The shape of the character - access(all) let shape: EntryIdentifier + access(all) let items: @[FungibleEntry] // The settings of the character access(all) let settings: {CreatureSettings: Int64} + // The bio prompts of the character + access(all) let bioPrompts: [String] view init( - attributes: Attributes, - defence: Defence, - potentiality: Potentiality, - features: [EntryIdentifier], - abilities: [EntryIdentifier], - items: [EntryIdentifier], shape: EntryIdentifier, - settings: {CreatureSettings: Int64} + features: [EntryIdentifier], + bioPrompts: [String] ) { - self.attributes = attributes - self.defence = defence - self.potentiality = potentiality self.features = features - self.abilities = abilities - self.items = items self.shape = shape - self.settings = settings - + self.settings = {} + self.bioPrompts = bioPrompts + self.items <- [] // check identifiers assert(self.shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") for featureIdentifier in features { assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") } - 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") - } + } + + access(all) view + fun borrowSelfAttributes(): &Attributes { + return &self.baseAttributes + } + + access(all) view + fun borrowSelfDefence(): &Defence { + return &self.baseDefence + } + + access(all) view + fun borrowSelfPotentiality(): &Potentiality { + return &self.baseDefence } } From 72e215ca7b9ddcc4f9d21acdfb2198045bf796a9 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sat, 17 May 2025 23:37:56 +0800 Subject: [PATCH 08/45] refactor: streamline status management and collection handling in FGameMishal contract; replace direct status access with borrow functions, enhance collection interfaces, and improve event emissions for unit status updates --- cadence/contracts/FGameMishal.cdc | 170 +++++++++++++++++++++++------- 1 file changed, 129 insertions(+), 41 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 217a5f7..498ea80 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -766,11 +766,7 @@ access(all) contract FGameMishal { } access(all) resource interface ComposableUnitStatusCarrier: LiveUnitStatusCarrier { - access(all) let status: UnitStatus - - access(all) view fun borrowStatus(): &UnitStatus { - return &self.status - } + access(all) view fun borrowStatus(): &UnitStatus access(all) fun borrowAttributesElements(): [&Attributes] access(all) fun borrowDefenceElements(): [&Defence] @@ -778,6 +774,8 @@ access(all) contract FGameMishal { access(contract) fun applyStatus() { + let status = self.borrowStatus() + // Calculate the new attributes of the unit let attributes = self.borrowAttributesElements() let newAttributes = Attributes(strength: 0, vitality: 0, spirit: 0) @@ -786,7 +784,7 @@ access(all) contract FGameMishal { newAttributes.addVitality(attribute.vitality) newAttributes.addSpirit(attribute.spirit) } - self.status.setAttributes(newAttributes) + status.setAttributes(newAttributes) // Calculate the new defence of the unit let defence = self.borrowDefenceElements() @@ -796,7 +794,7 @@ access(all) contract FGameMishal { newDefence.addEndurance(one.endurance) newDefence.addResistance(one.resistance) } - self.status.setDefence(newDefence) + status.setDefence(newDefence) // Calculate the new potentiality of the unit let potentiality = self.borrowPotentialityElements() @@ -804,10 +802,15 @@ access(all) contract FGameMishal { for one in potentiality { newPotentiality.add(one.initial) } - self.status.setPotentiality(newPotentiality) + status.setPotentiality(newPotentiality) // Emit the event - emit UnitStatusApplied(self.uuid, self.status.attributes, self.status.defence, self.status.potentiality) + emit UnitStatusApplied( + self.uuid, + status.attributes.copy() as! Attributes, + status.defence.copy() as! Defence, + status.potentiality.copy() as! Potentiality + ) } } @@ -1240,7 +1243,7 @@ access(all) contract FGameMishal { } } - access(all) + access(Manage) 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" @@ -1272,6 +1275,11 @@ access(all) contract FGameMishal { } } + access(Manage) view + fun borrowEditableEntry(_ id: String): auth(FungibleToken.Withdraw) &FungibleEntry? { + return &self.entries[id] + } + access(self) view fun isNonFungible(_ category: LibraryCategory): Bool { switch category { @@ -1336,31 +1344,36 @@ access(all) contract FGameMishal { } } - access(all) resource interface UnitStaticBasicCapabiltiesCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { - access(all) let collection: @EntryCollection + access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { + access(all) view fun borrowCollection(): &EntryCollection access(all) view fun getAbilitiesLength(): Int { - return self.collection.getLengthByCategory(LibraryCategory.ABILITY) + let collection = self.borrowCollection() + return collection.getLengthByCategory(LibraryCategory.ABILITY) } access(all) fun getAbilityIdentifiers(): [EntryIdentifier] { - return self.collection.getEntryIdentifiers(LibraryCategory.ABILITY) + let collection = self.borrowCollection() + return collection.getEntryIdentifiers(LibraryCategory.ABILITY) } access(all) view fun getItemsLength(): Int { - return self.collection.getLengthByCategory(LibraryCategory.ITEM) + let collection = self.borrowCollection() + return collection.getLengthByCategory(LibraryCategory.ITEM) } access(all) fun borrowItemEntries(): [&FungibleEntry] { - return self.collection.borrowEnties(LibraryCategory.ITEM) + let collection = self.borrowCollection() + return collection.borrowEnties(LibraryCategory.ITEM) } access(all) view fun borrowShape(): &Shape? { - let shape = self.collection.getKeysByCategory(LibraryCategory.SHAPE) + let collection = self.borrowCollection() + let shape = collection.getKeysByCategory(LibraryCategory.SHAPE) if shape.length > 0 { - if let entry = self.collection.borrowEntryByID(shape[0]) { + if let entry = collection.borrowEntryByID(shape[0]) { return entry.identifier.borrowShape() } } @@ -1369,7 +1382,7 @@ access(all) contract FGameMishal { } // The Feature resource is used to define the features of the entry. - access(all) resource Feature: Nameable, OptionalStatusCarrier, UnitStaticBasicCapabiltiesCarrier, EffectsCarrier { + access(all) resource Feature: Nameable, OptionalStatusCarrier, UnitCollectionBaseCarrier, EffectsCarrier { access(all) let name: String access(all) let tags: [String] access(all) let attributes: Attributes? @@ -1427,6 +1440,11 @@ access(all) contract FGameMishal { } } } + + access(all) view + fun borrowCollection(): &EntryCollection { + return &self.collection + } } access(all) resource interface FeaturesCarrier { @@ -1453,14 +1471,16 @@ access(all) contract FGameMishal { } } - access(all) resource interface UnitStaticCapabiltiesCarrier: UnitStaticBasicCapabiltiesCarrier, FeaturesCarrier { + access(all) resource interface UnitCollectionCarrier: UnitCollectionBaseCarrier, FeaturesCarrier { access(all) view fun getFeaturesLength(): Int { - return self.collection.getLengthByCategory(LibraryCategory.FEATURE) + let collection = self.borrowCollection() + return collection.getLengthByCategory(LibraryCategory.FEATURE) } access(all) fun getFeatureIdentifiers(): [EntryIdentifier] { - return self.collection.getEntryIdentifiers(LibraryCategory.FEATURE) + let collection = self.borrowCollection() + return collection.getEntryIdentifiers(LibraryCategory.FEATURE) } } @@ -1572,7 +1592,7 @@ access(all) contract FGameMishal { } } - access(all) resource Creature: Nameable, CreatureInterface, UnitStaticCapabiltiesCarrier, BioCarrier { + access(all) resource Creature: Nameable, CreatureInterface, UnitCollectionCarrier, BioCarrier { access(all) let name: String access(all) let tags: [String] access(all) let collection: @EntryCollection @@ -1645,6 +1665,16 @@ access(all) contract FGameMishal { self.applyStatus() } + access(all) view + fun borrowCollection(): &EntryCollection { + return &self.collection + } + + access(all) view + fun borrowStatus(): &UnitStatus { + return &self.status + } + access(all) view fun borrowSelfAttributes(): &Attributes { return &self.baseAttributes @@ -1666,10 +1696,23 @@ access(all) contract FGameMishal { access(all) resource CultivableProperty { access(contract) let library: Address + // --- Exported Properties --- + access(all) let attributes: Attributes access(all) let defence: Defence access(all) let potentiality: Potentiality + // The merged status of the character + access(all) let status: UnitStatus + + // --- Cultivable Property --- + + access(all) var potentialityUsed: UInt64 + access(all) var potentialityObtained: UInt64 + + // The collection of the character + access(all) let collection: @EntryCollection + view init( _ library: Address ) { @@ -1682,56 +1725,101 @@ access(all) contract FGameMishal { self.attributes = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) self.defence = Defence(physical: initDef, endurance: initDef, resistance: initDef) self.potentiality = Potentiality(initial: initPtt) + + self.status = UnitStatus( + attributes: self.attributes, + defence: self.defence, + potentiality: self.potentiality + ) + + self.potentialityUsed = 0 + self.potentialityObtained = 0 + + self.collection <- create EntryCollection() + } + + 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) view + fun borrowStatus(): &UnitStatus { + return &self.status + } + + access(Manage) view + fun borrowCollection(): auth(Manage) &EntryCollection { + return &self.collection } } // The Pawn resource is refered to as the character in the game. - access(all) resource Pawn: CreatureInterface, BioCarrier { - // The shape of the character - access(all) let shape: EntryIdentifier - // The features of the character - access(all) let features: [EntryIdentifier] - // The merged status of the character - access(all) let status: UnitStatus - // The items of the character - access(all) let items: @[FungibleEntry] + access(all) resource Pawn: CreatureInterface, UnitCollectionCarrier, BioCarrier { + // The cultivable property of the character + access(all) let cultivable: @CultivableProperty // The settings of the character access(all) let settings: {CreatureSettings: Int64} // The bio prompts of the character access(all) let bioPrompts: [String] - view init( + init( + _ library: Address, shape: EntryIdentifier, features: [EntryIdentifier], bioPrompts: [String] ) { - self.features = features - self.shape = shape self.settings = {} self.bioPrompts = bioPrompts - self.items <- [] + self.cultivable <- create CultivableProperty(library) + + assert(shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") + self.cultivable.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) - // check identifiers - assert(self.shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") for featureIdentifier in features { assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") + self.cultivable.collection.deposit(entry: <-create FungibleEntry(identifier: featureIdentifier, amount: 1.0)) } + + self.applyStatus() } access(all) view fun borrowSelfAttributes(): &Attributes { - return &self.baseAttributes + return self.cultivable.borrowAttributes() } access(all) view fun borrowSelfDefence(): &Defence { - return &self.baseDefence + return self.cultivable.borrowDefence() } access(all) view fun borrowSelfPotentiality(): &Potentiality { - return &self.baseDefence + return self.cultivable.borrowPotentiality() + } + + access(all) view + fun borrowStatus(): &UnitStatus { + return self.cultivable.borrowStatus() } + + access(all) view + fun borrowCollection(): &EntryCollection { + return self.cultivable.borrowCollection() + } + + // ---- Pawn Cultivable Functions ---- } // ---- Public Functions ---- From e9a14d8bfc6ecdcfa9c975662c0b55ef5ec14e95 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sat, 17 May 2025 23:43:08 +0800 Subject: [PATCH 09/45] refactor: update borrowCollection access control in FGameMishal contract; restrict access to Manage role and streamline collection handling for improved security and clarity --- cadence/contracts/FGameMishal.cdc | 36 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 498ea80..5660a60 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1345,7 +1345,7 @@ access(all) contract FGameMishal { } access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { - access(all) view fun borrowCollection(): &EntryCollection + access(Manage) view fun borrowCollection(): auth(Manage) &EntryCollection access(all) view fun getAbilitiesLength(): Int { let collection = self.borrowCollection() @@ -1441,8 +1441,8 @@ access(all) contract FGameMishal { } } - access(all) view - fun borrowCollection(): &EntryCollection { + access(Manage) view + fun borrowCollection(): auth(Manage) &EntryCollection { return &self.collection } } @@ -1612,6 +1612,8 @@ access(all) contract FGameMishal { init( name: String, tags: [String], + shape: EntryIdentifier, + settings: {CreatureSettings: Int64}, attributes: Attributes, defence: Defence, potentiality: Potentiality, @@ -1619,8 +1621,6 @@ access(all) contract FGameMishal { abilities: [EntryIdentifier], items: [EntryIdentifier], itemAmounts: {String: UFix64}, - shape: EntryIdentifier, - settings: {CreatureSettings: Int64}, bioPrompts: [String] ) { self.name = name @@ -1665,11 +1665,6 @@ access(all) contract FGameMishal { self.applyStatus() } - access(all) view - fun borrowCollection(): &EntryCollection { - return &self.collection - } - access(all) view fun borrowStatus(): &UnitStatus { return &self.status @@ -1689,6 +1684,11 @@ access(all) contract FGameMishal { fun borrowSelfPotentiality(): &Potentiality { return &self.basePotentiality } + + access(Manage) view + fun borrowCollection(): auth(Manage) &EntryCollection { + return &self.collection + } } // ------------ Player ------------ @@ -1777,6 +1777,8 @@ access(all) contract FGameMishal { _ library: Address, shape: EntryIdentifier, features: [EntryIdentifier], + items: [EntryIdentifier], + itemAmounts: {String: UFix64}, bioPrompts: [String] ) { self.settings = {} @@ -1791,6 +1793,16 @@ access(all) contract FGameMishal { self.cultivable.collection.deposit(entry: <-create FungibleEntry(identifier: featureIdentifier, 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.cultivable.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) + } + } + self.applyStatus() } @@ -1814,8 +1826,8 @@ access(all) contract FGameMishal { return self.cultivable.borrowStatus() } - access(all) view - fun borrowCollection(): &EntryCollection { + access(Manage) view + fun borrowCollection(): auth(Manage) &EntryCollection { return self.cultivable.borrowCollection() } From d9ab0ce304ffa32395db6e71043824976b04c234 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 00:11:26 +0800 Subject: [PATCH 10/45] refactor: enhance EntryContainer and related interfaces in FGameMishal contract; introduce new methods for borrowing entries and equipped items, improve item equippability checks, and streamline resource management for better usability --- cadence/contracts/FGameMishal.cdc | 85 +++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 5660a60..9c54b9a 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1165,8 +1165,14 @@ access(all) contract FGameMishal { } } + // 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? + } + // The EntryCollection resource is used to store the entries. - access(all) resource EntryCollection { + access(all) resource EntryCollection: EntryContainer { access(all) let entries: @{String: FungibleEntry} access(all) let categories: {LibraryCategory: [String]} @@ -1196,7 +1202,7 @@ access(all) contract FGameMishal { } access(all) - fun borrowEnties(_ category: LibraryCategory?): [&FungibleEntry] { + fun borrowEntries(_ category: LibraryCategory?): [&FungibleEntry] { let ret: [&FungibleEntry] = [] let keys = category == nil ? self.entries.keys : self.getKeysByCategory(category!) for id in keys { @@ -1293,7 +1299,7 @@ access(all) contract FGameMishal { } } - access(all) resource interface AbilitiesCarrier { + access(all) resource interface AbilitiesCarrier: EntryContainer { access(all) view fun getAbilitiesLength(): Int access(all) fun getAbilityIdentifiers(): [EntryIdentifier] @@ -1317,7 +1323,7 @@ access(all) contract FGameMishal { } } - access(all) resource interface ItemsCarrier { + access(all) resource interface ItemsCarrier: EntryContainer { access(all) view fun getItemsLength(): Int access(all) fun borrowItemEntries(): [&FungibleEntry] @@ -1344,9 +1350,78 @@ access(all) contract FGameMishal { } } + access(all) resource interface EquippedItemsCarrier: ItemsCarrier { + access(all) view fun getSlotsAll(): {EquipSlot: UInt8} + access(all) view fun borrowSlotsOccupied(): &{EquipSlot: [String]} + + 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 + } + + 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 + } + + access(all) view + fun isItemEquippable(_ item: EntryIdentifier): Bool { + if let itemRef = item.borrowItem() { + 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 + } + return false + } + } + access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { access(Manage) view fun borrowCollection(): auth(Manage) &EntryCollection + access(all) view fun borrowEntryByID(_ id: String): &FungibleEntry? { + let collection = self.borrowCollection() + return collection.borrowEntryByID(id) + } + access(all) view fun getAbilitiesLength(): Int { let collection = self.borrowCollection() return collection.getLengthByCategory(LibraryCategory.ABILITY) @@ -1366,7 +1441,7 @@ access(all) contract FGameMishal { access(all) fun borrowItemEntries(): [&FungibleEntry] { let collection = self.borrowCollection() - return collection.borrowEnties(LibraryCategory.ITEM) + return collection.borrowEntries(LibraryCategory.ITEM) } access(all) view fun borrowShape(): &Shape? { From d1395bfb3e40c593d0328746d9671747f7452f87 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 00:36:29 +0800 Subject: [PATCH 11/45] feat: add item equip and unequip functionality in FGameMishal contract; introduce new events for item management, enhance slots handling, and improve equippability checks for better gameplay experience --- cadence/contracts/FGameMishal.cdc | 93 ++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 9c54b9a..21147fb 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -30,6 +30,9 @@ access(all) contract FGameMishal { 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) + access(all) event CreatureItemUnequipped(_ library: Address, _ item: String, _ owner: Address?, itemUUID: UInt64) + // ----- Contract Level Variables ----- // The counter variable for the library items @@ -575,6 +578,8 @@ access(all) contract FGameMishal { access(all) var defence: Defence access(all) var potentiality: Potentiality + access(all) var slotsOccupied: {EquipSlot: [String]} + view init( attributes: Attributes, defence: Defence, @@ -583,6 +588,7 @@ access(all) contract FGameMishal { self.attributes = attributes self.defence = defence self.potentiality = potentiality + self.slotsOccupied = {} } access(all) fun copy(): {Copyable} { return self } @@ -1352,7 +1358,7 @@ access(all) contract FGameMishal { access(all) resource interface EquippedItemsCarrier: ItemsCarrier { access(all) view fun getSlotsAll(): {EquipSlot: UInt8} - access(all) view fun borrowSlotsOccupied(): &{EquipSlot: [String]} + access(Manage) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} access(all) fun borrowEquippedItems(): [&Item] { @@ -1398,19 +1404,76 @@ access(all) contract FGameMishal { access(all) view fun isItemEquippable(_ item: EntryIdentifier): Bool { if let itemRef = item.borrowItem() { - 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 self._isItemEquippable(itemRef) + } + return false + } + + access(Manage) + 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 } - return true } - return false + + emit CreatureItemEquipped( + item.library, + itemId, + self.owner?.address, + itemUUID: itemRef.uuid, + ) + } + + access(Manage) + 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 + } + } + } + + access(contract) view + 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 } } @@ -1573,7 +1636,7 @@ access(all) contract FGameMishal { } } - access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, ItemsCarrier, FeaturesCarrier, ShapeCarrier { + access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, EquippedItemsCarrier, FeaturesCarrier, ShapeCarrier { access(all) view fun borrowSelfAttributes(): &Attributes access(all) view fun borrowSelfDefence(): &Defence access(all) view fun borrowSelfPotentiality(): &Potentiality @@ -1603,7 +1666,7 @@ access(all) contract FGameMishal { } // From Items - let items = self.borrowItems() + let items = self.borrowEquippedItems() for item in items { if item.hasAttributes() { if let attributes = item.borrowAttributes() { @@ -1629,7 +1692,7 @@ access(all) contract FGameMishal { } // From Items - let items = self.borrowItems() + let items = self.borrowEquippedItems() for item in items { if item.hasDefence() { if let defence = item.borrowDefence() { @@ -1655,7 +1718,7 @@ access(all) contract FGameMishal { } // From Items - let items = self.borrowItems() + let items = self.borrowEquippedItems() for item in items { if item.hasPotentiality() { if let potentiality = item.borrowPotentiality() { From 6ee22153d84f0888987a2ad6d5abbfa0690d90e5 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 14:40:02 +0800 Subject: [PATCH 12/45] feat: add new functions for slot management and item equippability checks in FGameMishal contract; enhance borrowing capabilities for slots and improve overall item handling for better gameplay experience --- cadence/contracts/FGameMishal.cdc | 90 +++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 21147fb..c877c01 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -608,6 +608,11 @@ access(all) contract FGameMishal { self.potentiality = potentiality } + access(contract) view + fun borrowWritableSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { + return &self.slotsOccupied + } + access(all) view fun borrowAttributes(): &Attributes { return &self.attributes @@ -884,6 +889,16 @@ access(all) contract FGameMishal { 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 + } } access(all) resource Ability: AttributeCarrier, EffectsCarrier, Nameable { @@ -1357,8 +1372,22 @@ access(all) contract FGameMishal { } access(all) resource interface EquippedItemsCarrier: ItemsCarrier { - access(all) view fun getSlotsAll(): {EquipSlot: UInt8} access(Manage) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} + access(all) fun getSlotsAll(): {EquipSlot: UInt8} + + 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 + } access(all) fun borrowEquippedItems(): [&Item] { @@ -1387,21 +1416,7 @@ access(all) contract FGameMishal { return ret } - 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 - } - - access(all) view + access(all) fun isItemEquippable(_ item: EntryIdentifier): Bool { if let itemRef = item.borrowItem() { return self._isItemEquippable(itemRef) @@ -1461,7 +1476,7 @@ access(all) contract FGameMishal { } } - access(contract) view + access(contract) fun _isItemEquippable(_ itemRef: &Item): Bool { let allSlots = self.getSlotsAll() let occupiedSlots = self.borrowSlotsOccupied() @@ -1728,6 +1743,37 @@ access(all) contract FGameMishal { } return ret } + + 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 + } } access(all) resource Creature: Nameable, CreatureInterface, UnitCollectionCarrier, BioCarrier { @@ -1827,6 +1873,11 @@ access(all) contract FGameMishal { fun borrowCollection(): auth(Manage) &EntryCollection { return &self.collection } + + access(Manage) view + fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { + return self.status.borrowWritableSlotsOccupied() + } } // ------------ Player ------------ @@ -1969,6 +2020,11 @@ access(all) contract FGameMishal { return self.cultivable.borrowCollection() } + access(Manage) view + fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { + return self.cultivable.borrowStatus().borrowWritableSlotsOccupied() + } + // ---- Pawn Cultivable Functions ---- } From e163376168dc367cf65779f865043b18f3908bd3 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 15:46:50 +0800 Subject: [PATCH 13/45] feat: enhance FGameMishal contract with new events and methods for attribute and defence management; introduce functionality for updating settings, recovering health, and upgrading attributes, improving gameplay dynamics and character management --- cadence/contracts/FGameMishal.cdc | 393 +++++++++++++++++++++--------- 1 file changed, 279 insertions(+), 114 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index c877c01..b4750a4 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -33,6 +33,14 @@ access(all) contract FGameMishal { access(all) event CreatureItemEquipped(_ library: Address, _ item: String, _ owner: Address?, itemUUID: UInt64) access(all) event CreatureItemUnequipped(_ library: Address, _ item: String, _ owner: Address?, itemUUID: UInt64) + access(all) event CreatureSettingUpdated(_ type: String, _ uuid: UInt64, _ setting: UInt8, _ value: Int64) + + access(all) event PawnPotentialityGained(_ owner: Address?, _ amount: UInt64, uuid: UInt64) + access(all) event PawnAttributeUpgraded(_ owner: Address?, _ type: UInt8, _ amount: 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) + // ----- Contract Level Variables ----- // The counter variable for the library items @@ -49,6 +57,25 @@ access(all) contract FGameMishal { access(all) case INIT_DEFENCE_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 @@ -497,34 +524,46 @@ access(all) contract FGameMishal { access(all) fun copy(): {Copyable} { return self } - access(contract) - fun setStrength(_ strength: Int64) { - self.strength = strength - } - - access(contract) - fun addStrength(_ strength: Int64) { - self.strength = self.strength + strength - } - - access(contract) - fun setVitality(_ vitality: Int64) { - self.vitality = vitality - } - - access(contract) - fun addVitality(_ vitality: Int64) { - self.vitality = self.vitality + vitality + 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 + } } - access(contract) - fun setSpirit(_ spirit: Int64) { - self.spirit = spirit + access(Manage) + 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") + } } - access(contract) - fun addSpirit(_ spirit: Int64) { - self.spirit = self.spirit + spirit + access(Manage) + fun addValue(_ type: AttributeType, _ value: Int64) { + switch type { + case AttributeType.STRENGTH: + self.strength = self.strength + value + case AttributeType.VITALITY: + self.vitality = self.vitality + value + case AttributeType.SPIRIT: + self.spirit = self.spirit + value + default: + panic("Invalid attribute type") + } } } @@ -541,34 +580,46 @@ access(all) contract FGameMishal { access(all) fun copy(): {Copyable} { return self } - access(contract) - fun setPhysical(_ physical: Int64) { - self.physical = physical - } - - access(contract) - fun addPhysical(_ physical: Int64) { - self.physical = self.physical + physical - } - - access(contract) - fun setEndurance(_ endurance: Int64) { - self.endurance = endurance - } - - access(contract) - fun addEndurance(_ endurance: Int64) { - self.endurance = self.endurance + endurance + 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(contract) - fun setResistance(_ resistance: Int64) { - self.resistance = resistance + access(Manage) + 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") + } } - access(contract) - fun addResistance(_ resistance: Int64) { - self.resistance = self.resistance + resistance + access(Manage) + fun addValue(_ type: DefenceType, _ value: Int64) { + switch type { + case DefenceType.PHYSICAL: + self.physical = self.physical + value + case DefenceType.ENDURANCE: + self.endurance = self.endurance + value + case DefenceType.RESISTANCE: + self.resistance = self.resistance + value + default: + panic("Invalid defence type") + } } } @@ -791,9 +842,9 @@ access(all) contract FGameMishal { let attributes = self.borrowAttributesElements() let newAttributes = Attributes(strength: 0, vitality: 0, spirit: 0) for attribute in attributes { - newAttributes.addStrength(attribute.strength) - newAttributes.addVitality(attribute.vitality) - newAttributes.addSpirit(attribute.spirit) + newAttributes.addValue(AttributeType.STRENGTH, attribute.strength) + newAttributes.addValue(AttributeType.VITALITY, attribute.vitality) + newAttributes.addValue(AttributeType.SPIRIT, attribute.spirit) } status.setAttributes(newAttributes) @@ -801,9 +852,9 @@ access(all) contract FGameMishal { let defence = self.borrowDefenceElements() let newDefence = Defence(physical: 0, endurance: 0, resistance: 0) for one in defence { - newDefence.addPhysical(one.physical) - newDefence.addEndurance(one.endurance) - newDefence.addResistance(one.resistance) + newDefence.addValue(DefenceType.PHYSICAL, one.physical) + newDefence.addValue(DefenceType.ENDURANCE, one.endurance) + newDefence.addValue(DefenceType.RESISTANCE, one.resistance) } status.setDefence(newDefence) @@ -940,6 +991,18 @@ access(all) contract FGameMishal { fun hasSettings(): Bool { return self.settings.length > 0 } + + access(Manage) + fun updateSetting(_ setting: CreatureSettings, _ value: Int64) { + self.settings[setting] = value + + emit CreatureSettingUpdated( + self.getType().identifier, + self.uuid, + setting.rawValue, + value + ) + } } access(all) resource Shape: CreatureSettingsCarrier, Nameable { @@ -1882,81 +1945,152 @@ access(all) contract FGameMishal { // ------------ Player ------------ - access(all) resource CultivableProperty { - access(contract) let library: Address + access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { + access(Manage) view fun borrowHealth(): auth(Mutate) &Attributes - // --- Exported Properties --- + // ---- Gameplay Methods, Write --- - access(all) let attributes: Attributes - access(all) let defence: Defence - access(all) let potentiality: Potentiality + access(Manage) + fun resetHealth() { + let health = self.borrowHealth() + let status = self.borrowStatus() - // The merged status of the character - access(all) let status: UnitStatus + health.setValue(AttributeType.STRENGTH, status.attributes.strength) + health.setValue(AttributeType.VITALITY, status.attributes.vitality) + health.setValue(AttributeType.SPIRIT, status.attributes.spirit) - // --- Cultivable Property --- + emit PawnHealthReset( + self.owner?.address ?? panic("Owner not found"), + health.strength, + health.vitality, + health.spirit, + uuid: self.uuid + ) + } - access(all) var potentialityUsed: UInt64 - access(all) var potentialityObtained: UInt64 + access(Manage) + fun recoverHealth(_ type: AttributeType, _ amount: Int64) { + let health = self.borrowHealth() + let status = self.borrowStatus() - // The collection of the character - access(all) let collection: @EntryCollection + let maxAttr = status.attributes.getValue(type) + let currentAttr = health.getValue(type) - view init( - _ library: Address - ) { - self.library = library - let lib = FGameMishal.borrowLibrary(library) ?? panic("Library not found") - let initPtt = lib.settings[LibrarySettings.INIT_POTENTIALITY] ?? 36 - let initDef = lib.settings[LibrarySettings.INIT_DEFENCE_VALUE] ?? 0 - let initAttr = lib.settings[LibrarySettings.INIT_ATTRIBUTE_VALUE] ?? 3 + var recoverAmount = amount + if currentAttr + amount > maxAttr { + recoverAmount = maxAttr - currentAttr + } - self.attributes = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) - self.defence = Defence(physical: initDef, endurance: initDef, resistance: initDef) - self.potentiality = Potentiality(initial: initPtt) + health.setValue(type, currentAttr + recoverAmount) - self.status = UnitStatus( - attributes: self.attributes, - defence: self.defence, - potentiality: self.potentiality + emit PawnHealthRecovered( + self.owner?.address ?? panic("Owner not found"), + type.rawValue, + recoverAmount, + uuid: self.uuid ) + } + } - self.potentialityUsed = 0 - self.potentialityObtained = 0 + access(all) resource interface CultivableUnit: ComposableUnitStatusCarrier, UnitCollectionCarrier { + // The potentiality used by the character + access(all) var potentialityUsed: UInt64 + // The potentiality obtained by the character + access(all) var potentialityObtained: UInt64 - self.collection <- create EntryCollection() - } + // ---- Interface ---- - access(all) view - fun borrowAttributes(): &Attributes { - return &self.attributes - } + access(Manage) view fun borrowCultivableAttributes(): auth(Mutate) &Attributes + + // ---- Cultivable Methods, Read ---- access(all) view - fun borrowDefence(): &Defence { - return &self.defence + fun getUsablePotentiality(): UInt64 { + let status = self.borrowStatus() + return UInt64(status.potentiality.initial) + self.potentialityObtained - self.potentialityUsed } - access(all) view - fun borrowPotentiality(): &Potentiality { - return &self.potentiality + // --- Cultivable Methods, Write --- + + access(Manage) + fun gainPotentiality(_ amount: UInt64) { + self.potentialityObtained = self.potentialityObtained + amount + + emit PawnPotentialityGained( + self.owner?.address ?? panic("Owner not found"), + amount, + uuid: self.uuid + ) } access(all) view - fun borrowStatus(): &UnitStatus { - return &self.status + 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 } - access(Manage) view - fun borrowCollection(): auth(Manage) &EntryCollection { - return &self.collection + access(Manage) + 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) + let consume = currentValue * 3 + + // consume potentiality + self.potentialityUsed = self.potentialityUsed + UInt64(consume) + + // upgrade attribute + attributes.addValue(type, 1) + + toUpgrade = toUpgrade - 1 + } + + emit PawnAttributeUpgraded( + self.owner?.address ?? panic("Owner not found"), + type.rawValue, + amount, + uuid: self.uuid + ) } } // The Pawn resource is refered to as the character in the game. - access(all) resource Pawn: CreatureInterface, UnitCollectionCarrier, BioCarrier { - // The cultivable property of the character - access(all) let cultivable: @CultivableProperty + access(all) resource Pawn: CreatureInterface, BioCarrier, PlayableUnit, CultivableUnit { + access(contract) let library: Address + // Initial defence(will not be changed after initialization) + access(self) let initDefence: Defence + // 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 + // The health of the character, can be damaged + access(all) var health: Attributes + + // --- Cultivable Property --- + + // The potentiality used by the character + access(all) var potentialityUsed: UInt64 + // The potentiality obtained by the character + access(all) var potentialityObtained: UInt64 + + // Cultivable attributes, can be upgraded by potentiality + access(all) let attributes: Attributes + + // The collection of the character + access(all) let collection: @EntryCollection + + // --- Settings --- + // The settings of the character access(all) let settings: {CreatureSettings: Int64} // The bio prompts of the character @@ -1970,16 +2104,37 @@ access(all) contract FGameMishal { itemAmounts: {String: UFix64}, bioPrompts: [String] ) { + self.library = library self.settings = {} self.bioPrompts = bioPrompts - self.cultivable <- create CultivableProperty(library) + + let lib = FGameMishal.borrowLibrary(library) ?? panic("Library not found") + let initPtt = lib.settings[LibrarySettings.INIT_POTENTIALITY] ?? 36 + let initDef = lib.settings[LibrarySettings.INIT_DEFENCE_VALUE] ?? 0 + let initAttr = lib.settings[LibrarySettings.INIT_ATTRIBUTE_VALUE] ?? 3 + + self.initDefence = Defence(physical: initDef, endurance: initDef, resistance: initDef) + self.initPotentiality = Potentiality(initial: initPtt) + self.attributes = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) + + self.status = UnitStatus( + attributes: self.attributes, + defence: self.initDefence, + potentiality: self.initPotentiality + ) + self.health = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) + + self.potentialityUsed = 0 + self.potentialityObtained = 0 + + self.collection <- create EntryCollection() assert(shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") - self.cultivable.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) + self.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) for featureIdentifier in features { assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") - self.cultivable.collection.deposit(entry: <-create FungibleEntry(identifier: featureIdentifier, amount: 1.0)) + self.collection.deposit(entry: <-create FungibleEntry(identifier: featureIdentifier, amount: 1.0)) } if items.length > 0 { @@ -1988,44 +2143,54 @@ access(all) contract FGameMishal { assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid") let id = itemIdentifier.getStringID() let amount = itemAmounts[id] ?? 0.0 - self.cultivable.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) + self.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) } } self.applyStatus() } + // ---- Interface Implementation ---- + access(all) view fun borrowSelfAttributes(): &Attributes { - return self.cultivable.borrowAttributes() + return self.borrowCultivableAttributes() + } + + access(Manage) view + fun borrowCultivableAttributes(): auth(Mutate) &Attributes { + return &self.attributes } access(all) view fun borrowSelfDefence(): &Defence { - return self.cultivable.borrowDefence() + return &self.initDefence } access(all) view fun borrowSelfPotentiality(): &Potentiality { - return self.cultivable.borrowPotentiality() + return &self.initPotentiality } access(all) view fun borrowStatus(): &UnitStatus { - return self.cultivable.borrowStatus() + return &self.status } access(Manage) view fun borrowCollection(): auth(Manage) &EntryCollection { - return self.cultivable.borrowCollection() + return &self.collection } access(Manage) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { - return self.cultivable.borrowStatus().borrowWritableSlotsOccupied() + return self.status.borrowWritableSlotsOccupied() } - // ---- Pawn Cultivable Functions ---- + access(Manage) view + fun borrowHealth(): auth(Mutate) &Attributes { + return &self.health + } } // ---- Public Functions ---- From 9f51776ad5e56ee6cd46b44a3cffa7f87838e341 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 16:11:47 +0800 Subject: [PATCH 14/45] feat: add PawnHealthDamaged event and enhance health management methods in FGameMishal contract; introduce isStunned and isDead checks, improve damage calculation logic, and ensure attributes cannot be negative for better gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 119 +++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 25 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index b4750a4..ac5a58c 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -40,6 +40,7 @@ access(all) contract FGameMishal { 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) // ----- Contract Level Variables ----- @@ -63,12 +64,12 @@ access(all) contract FGameMishal { 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 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 @@ -538,6 +539,7 @@ access(all) contract FGameMishal { } } + /// This method will not check if the value is negative access(Manage) fun setValue(_ type: AttributeType, _ value: Int64) { switch type { @@ -552,15 +554,30 @@ access(all) contract FGameMishal { } } + // For this method, the final value cannot be negative access(Manage) 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") } @@ -594,29 +611,32 @@ access(all) contract FGameMishal { } } - access(Manage) - fun setValue(_ type: DefenceType, _ value: Int64) { + access(all) view + fun getDefenceFrom(_ type: AttackType): Int64 { switch type { - case DefenceType.PHYSICAL: - self.physical = value - case DefenceType.ENDURANCE: - self.endurance = value - case DefenceType.RESISTANCE: - self.resistance = value + 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: - panic("Invalid defence type") + return 0 } } + // This method will not check if the value is negative access(Manage) - fun addValue(_ type: DefenceType, _ value: Int64) { + fun setValue(_ type: DefenceType, _ value: Int64) { switch type { case DefenceType.PHYSICAL: - self.physical = self.physical + value + self.physical = value case DefenceType.ENDURANCE: - self.endurance = self.endurance + value + self.endurance = value case DefenceType.RESISTANCE: - self.resistance = self.resistance + value + self.resistance = value default: panic("Invalid defence type") } @@ -842,9 +862,9 @@ access(all) contract FGameMishal { let attributes = self.borrowAttributesElements() let newAttributes = Attributes(strength: 0, vitality: 0, spirit: 0) for attribute in attributes { - newAttributes.addValue(AttributeType.STRENGTH, attribute.strength) - newAttributes.addValue(AttributeType.VITALITY, attribute.vitality) - newAttributes.addValue(AttributeType.SPIRIT, attribute.spirit) + 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) @@ -852,9 +872,9 @@ access(all) contract FGameMishal { let defence = self.borrowDefenceElements() let newDefence = Defence(physical: 0, endurance: 0, resistance: 0) for one in defence { - newDefence.addValue(DefenceType.PHYSICAL, one.physical) - newDefence.addValue(DefenceType.ENDURANCE, one.endurance) - newDefence.addValue(DefenceType.RESISTANCE, one.resistance) + 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) @@ -1948,6 +1968,20 @@ access(all) contract FGameMishal { access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { access(Manage) view fun borrowHealth(): auth(Mutate) &Attributes + // ---- Gameplay Methods, Read --- + + access(all) view + fun isStunned(): Bool { + let health = self.borrowHealth() + return health.strength <= 0 || health.vitality <= 0 || health.spirit <= 0 + } + + access(all) view + fun isDead(): Bool { + let health = self.borrowHealth() + return health.strength <= 0 && health.vitality <= 0 && health.spirit <= 0 + } + // ---- Gameplay Methods, Write --- access(Manage) @@ -1990,6 +2024,41 @@ access(all) contract FGameMishal { uuid: self.uuid ) } + + access(Manage) + fun damageHealth( + _ attacks: {AttackType: Int64}, + _ penetration: Int64, + _ type: AttributeType, + ) { + let health = self.borrowHealth() + let status = self.borrowStatus() + + var biggestDamage: Int64 = 0 + for attackType in attacks.keys { + let atk = attacks[attackType] ?? 0 + var def = status.defence.getDefenceFrom(attackType) + if def > penetration { + def = def - penetration + } else { + def = 0 + } + + let damage = atk - def + if damage > biggestDamage { + biggestDamage = damage + } + } + + health.addValue(type, -1 * biggestDamage) + + emit PawnHealthDamaged( + self.owner?.address ?? panic("Owner not found"), + type.rawValue, + biggestDamage, + uuid: self.uuid + ) + } } access(all) resource interface CultivableUnit: ComposableUnitStatusCarrier, UnitCollectionCarrier { From 5704131bd9d6bdff08b645b190b311a18e71da76 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 16:49:18 +0800 Subject: [PATCH 15/45] feat: enhance FGameMishal contract with new item gameplay methods; introduce deposit and withdraw functionalities for entries, improve uniqueness checks for entries, and update item equip/unequip events for better item management and gameplay experience --- cadence/contracts/FGameMishal.cdc | 119 ++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 16 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index ac5a58c..be55df3 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -30,8 +30,8 @@ access(all) contract FGameMishal { 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) - access(all) event CreatureItemUnequipped(_ library: Address, _ item: String, _ owner: Address?, itemUUID: 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) @@ -1273,6 +1273,25 @@ access(all) contract FGameMishal { access(all) resource interface EntryContainer { access(all) view fun borrowEntryByID(_ id: String): &FungibleEntry? + + access(all) + fun deposit(entry: @FungibleEntry) + + access(Manage) + 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. @@ -1342,7 +1361,7 @@ access(all) contract FGameMishal { } let uid = entry.identifier.getStringID() - if self.isNonFungible(entry.identifier.category) { + 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") } @@ -1369,7 +1388,7 @@ access(all) contract FGameMishal { } let ref = self.borrowEntryByID(id) ?? panic("EntryCollection.withdraw: Could not withdraw an entry with ID ".concat(id).concat(". Check if the entry exists.")) - if self.isNonFungible(ref.identifier.category) { + if FGameMishal.isEntryUniqueness(ref.identifier.category) { assert(amount == nil, message: "Non-fungible entry cannot have an amount") return <- self.entries.remove(key: id)! } else { @@ -1389,18 +1408,6 @@ access(all) contract FGameMishal { fun borrowEditableEntry(_ id: String): auth(FungibleToken.Withdraw) &FungibleEntry? { return &self.entries[id] } - - access(self) view - fun isNonFungible(_ category: LibraryCategory): Bool { - switch category { - case LibraryCategory.OBJECT: - return false - case LibraryCategory.ITEM: - return false - default: - return true - } - } } access(all) resource interface AbilitiesCarrier: EntryContainer { @@ -1452,6 +1459,23 @@ access(all) contract FGameMishal { fun hasItems(): Bool { return self.getItemsLength() > 0 } + + // --- Item Gameplay Methods --- + + access(Manage) + fun lootItem(_ entry: @FungibleEntry) { + pre { + entry.identifier.verify(LibraryCategory.ITEM): "Entry is not an item" + } + } + + access(Manage) + 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" + } + } } access(all) resource interface EquippedItemsCarrier: ItemsCarrier { @@ -1507,6 +1531,28 @@ access(all) contract FGameMishal { return false } + // --- Item Gameplay Methods --- + + access(Manage) + 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) + } + } + + access(Manage) + fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { + if self.hasItemEquipped(item) { + self.unequipItem(item) + } + return <- self.withdraw(item.getStringID(), amount: amount) + } + access(Manage) fun equipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) @@ -1534,6 +1580,7 @@ access(all) contract FGameMishal { itemId, self.owner?.address, itemUUID: itemRef.uuid, + uuid: self.uuid ) } @@ -1557,6 +1604,14 @@ access(all) contract FGameMishal { slots[slot] = newOccupied } } + + emit CreatureItemUnequipped( + item.library, + itemId, + self.owner?.address, + itemUUID: itemRef.uuid, + uuid: self.uuid + ) } access(contract) @@ -1578,11 +1633,25 @@ access(all) contract FGameMishal { access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { access(Manage) view fun borrowCollection(): auth(Manage) &EntryCollection + // --- Implement EntryContainer --- + access(all) view fun borrowEntryByID(_ id: String): &FungibleEntry? { let collection = self.borrowCollection() return collection.borrowEntryByID(id) } + access(all) + fun deposit(entry: @FungibleEntry) { + self.borrowCollection().deposit(entry: <-entry) + } + + access(Manage) + fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry { + return <- self.borrowCollection().withdraw(id, amount: amount) + } + + // --- Implement AbilitiesCarrier --- + access(all) view fun getAbilitiesLength(): Int { let collection = self.borrowCollection() return collection.getLengthByCategory(LibraryCategory.ABILITY) @@ -1594,6 +1663,8 @@ access(all) contract FGameMishal { return collection.getEntryIdentifiers(LibraryCategory.ABILITY) } + // --- Implement ItemsCarrier --- + access(all) view fun getItemsLength(): Int { let collection = self.borrowCollection() return collection.getLengthByCategory(LibraryCategory.ITEM) @@ -1605,6 +1676,8 @@ access(all) contract FGameMishal { return collection.borrowEntries(LibraryCategory.ITEM) } + // --- Implement ShapeCarrier --- + access(all) view fun borrowShape(): &Shape? { let collection = self.borrowCollection() let shape = collection.getKeysByCategory(LibraryCategory.SHAPE) @@ -1677,10 +1750,24 @@ access(all) contract FGameMishal { } } + // ---- Implement UnitCollectionBaseCarrier ---- + access(Manage) view fun borrowCollection(): auth(Manage) &EntryCollection { return &self.collection } + + // ---- Implement Item Gameplay Methods ---- + + access(Manage) + fun lootItem(_ entry: @FungibleEntry) { + self.deposit(entry: <-entry) + } + + access(Manage) + fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { + return <- self.withdraw(item.getStringID(), amount: amount) + } } access(all) resource interface FeaturesCarrier { From 51f4ea89c20400d43a3eeb92dc5321733f050382 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 17:40:14 +0800 Subject: [PATCH 16/45] feat: update FGameMishal contract to enhance role management and access control; replace Manage role with Creator and Host roles, introduce new events for potentiality consumption, and improve attribute cultivation methods for better gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 221 +++++++++++++++++++++--------- 1 file changed, 153 insertions(+), 68 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index be55df3..47a9824 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -16,8 +16,12 @@ import "FixesHeartbeat" access(all) contract FGameMishal { // Entitlements for the Editor role access(all) entitlement Editor; - // Entitlements for the Manage role (Like the host of the game) - access(all) entitlement Manage; + // Entitlements for the Creator role (Like the creator of the game) + access(all) entitlement Creator; + // 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; // Not used for now // ----- Events ----- @@ -42,6 +46,8 @@ access(all) contract FGameMishal { 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 PawnPotentialityConsumed(_ owner: Address?, _ consume: UInt64, _ usable: UInt64, _ used: UInt64, uuid: UInt64) + // ----- Contract Level Variables ----- // The counter variable for the library items @@ -540,7 +546,7 @@ access(all) contract FGameMishal { } /// This method will not check if the value is negative - access(Manage) + access(Host) fun setValue(_ type: AttributeType, _ value: Int64) { switch type { case AttributeType.STRENGTH: @@ -555,7 +561,7 @@ access(all) contract FGameMishal { } // For this method, the final value cannot be negative - access(Manage) + access(Host) fun addValue(_ type: AttributeType, _ value: Int64) { post { self.strength >= 0: "Strength cannot be negative" @@ -628,7 +634,7 @@ access(all) contract FGameMishal { } // This method will not check if the value is negative - access(Manage) + access(Host) fun setValue(_ type: DefenceType, _ value: Int64) { switch type { case DefenceType.PHYSICAL: @@ -724,13 +730,16 @@ access(all) contract FGameMishal { return self.value != nil } - access(Manage) + access(Host) fun setValue(value: UFix64) { self.value = value } - access(Manage) + access(Host) fun addValue(value: UFix64) { + post { + (self.value ?? 0.0) >= 0.0: "Value cannot be negative" + } self.value = (self.value ?? 0.0) + value } } @@ -743,12 +752,12 @@ access(all) contract FGameMishal { return self.effects.length > 0 } - access(Manage) + access(Host) fun addEffect(effect: String) { self.effects.append(effect) } - access(Manage) + access(Host) fun removeEffect(effect: String) { if let index = self.effects.firstIndex(of: effect) { let _ = self.effects.remove(at: index) @@ -1012,7 +1021,7 @@ access(all) contract FGameMishal { return self.settings.length > 0 } - access(Manage) + access(Host) fun updateSetting(_ setting: CreatureSettings, _ value: Int64) { self.settings[setting] = value @@ -1274,10 +1283,10 @@ access(all) contract FGameMishal { access(all) view fun borrowEntryByID(_ id: String): &FungibleEntry? - access(all) + access(Creator) fun deposit(entry: @FungibleEntry) - access(Manage) + access(Creator) fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry } @@ -1346,7 +1355,7 @@ access(all) contract FGameMishal { return &self.entries[id] } - access(all) + access(Creator) fun deposit(entry: @FungibleEntry) { pre { emit EntryDeposited( @@ -1372,7 +1381,7 @@ access(all) contract FGameMishal { } } - access(Manage) + access(Creator) 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" @@ -1404,7 +1413,7 @@ access(all) contract FGameMishal { } } - access(Manage) view + access(Host) view fun borrowEditableEntry(_ id: String): auth(FungibleToken.Withdraw) &FungibleEntry? { return &self.entries[id] } @@ -1462,14 +1471,14 @@ access(all) contract FGameMishal { // --- Item Gameplay Methods --- - access(Manage) + access(Host) fun lootItem(_ entry: @FungibleEntry) { pre { entry.identifier.verify(LibraryCategory.ITEM): "Entry is not an item" } } - access(Manage) + access(Host) 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" @@ -1479,7 +1488,7 @@ access(all) contract FGameMishal { } access(all) resource interface EquippedItemsCarrier: ItemsCarrier { - access(Manage) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} + access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} access(all) fun getSlotsAll(): {EquipSlot: UInt8} access(all) view @@ -1533,7 +1542,7 @@ access(all) contract FGameMishal { // --- Item Gameplay Methods --- - access(Manage) + access(Host) fun lootItem(_ entry: @FungibleEntry) { let itemId = entry.identifier let itemRef = itemId.borrowItem() ?? panic("Not Exists, Item: ".concat(itemId.getStringID())) @@ -1545,7 +1554,7 @@ access(all) contract FGameMishal { } } - access(Manage) + access(Host) fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { if self.hasItemEquipped(item) { self.unequipItem(item) @@ -1553,7 +1562,7 @@ access(all) contract FGameMishal { return <- self.withdraw(item.getStringID(), amount: amount) } - access(Manage) + access(Host) fun equipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) let itemId = item.getStringID() @@ -1584,7 +1593,7 @@ access(all) contract FGameMishal { ) } - access(Manage) + access(Host) fun unequipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) let itemId = item.getStringID() @@ -1631,55 +1640,60 @@ access(all) contract FGameMishal { } access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { - access(Manage) view fun borrowCollection(): auth(Manage) &EntryCollection + access(contract) view fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection // --- Implement EntryContainer --- + // Only Creator can borrow the collection + access(all) view fun borrowReadonlyCollection(): &EntryCollection { + return self.borrowWritableCollection() + } + access(all) view fun borrowEntryByID(_ id: String): &FungibleEntry? { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() return collection.borrowEntryByID(id) } - access(all) + access(Creator) fun deposit(entry: @FungibleEntry) { - self.borrowCollection().deposit(entry: <-entry) + self.borrowWritableCollection().deposit(entry: <-entry) } - access(Manage) + access(Creator) fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry { - return <- self.borrowCollection().withdraw(id, amount: amount) + return <- self.borrowWritableCollection().withdraw(id, amount: amount) } // --- Implement AbilitiesCarrier --- access(all) view fun getAbilitiesLength(): Int { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() return collection.getLengthByCategory(LibraryCategory.ABILITY) } access(all) fun getAbilityIdentifiers(): [EntryIdentifier] { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() return collection.getEntryIdentifiers(LibraryCategory.ABILITY) } // --- Implement ItemsCarrier --- access(all) view fun getItemsLength(): Int { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() return collection.getLengthByCategory(LibraryCategory.ITEM) } access(all) fun borrowItemEntries(): [&FungibleEntry] { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() return collection.borrowEntries(LibraryCategory.ITEM) } // --- Implement ShapeCarrier --- access(all) view fun borrowShape(): &Shape? { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() let shape = collection.getKeysByCategory(LibraryCategory.SHAPE) if shape.length > 0 { if let entry = collection.borrowEntryByID(shape[0]) { @@ -1752,19 +1766,19 @@ access(all) contract FGameMishal { // ---- Implement UnitCollectionBaseCarrier ---- - access(Manage) view - fun borrowCollection(): auth(Manage) &EntryCollection { + access(contract) view + fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { return &self.collection } // ---- Implement Item Gameplay Methods ---- - access(Manage) + access(Host) fun lootItem(_ entry: @FungibleEntry) { self.deposit(entry: <-entry) } - access(Manage) + access(Host) fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { return <- self.withdraw(item.getStringID(), amount: amount) } @@ -1796,13 +1810,13 @@ access(all) contract FGameMishal { access(all) resource interface UnitCollectionCarrier: UnitCollectionBaseCarrier, FeaturesCarrier { access(all) view fun getFeaturesLength(): Int { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() return collection.getLengthByCategory(LibraryCategory.FEATURE) } access(all) fun getFeatureIdentifiers(): [EntryIdentifier] { - let collection = self.borrowCollection() + let collection = self.borrowReadonlyCollection() return collection.getEntryIdentifiers(LibraryCategory.FEATURE) } } @@ -1815,7 +1829,7 @@ access(all) contract FGameMishal { return &self.bioPrompts } - access(Manage) + access(Host) fun addBioPrompt(_ prompt: String) { self.bioPrompts.append(prompt) } @@ -2039,12 +2053,12 @@ access(all) contract FGameMishal { return &self.basePotentiality } - access(Manage) view - fun borrowCollection(): auth(Manage) &EntryCollection { + access(contract) view + fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { return &self.collection } - access(Manage) view + access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { return self.status.borrowWritableSlotsOccupied() } @@ -2053,7 +2067,7 @@ access(all) contract FGameMishal { // ------------ Player ------------ access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { - access(Manage) view fun borrowHealth(): auth(Mutate) &Attributes + access(Host) view fun borrowHealth(): auth(Mutate) &Attributes // ---- Gameplay Methods, Read --- @@ -2071,7 +2085,7 @@ access(all) contract FGameMishal { // ---- Gameplay Methods, Write --- - access(Manage) + access(Host) fun resetHealth() { let health = self.borrowHealth() let status = self.borrowStatus() @@ -2089,7 +2103,7 @@ access(all) contract FGameMishal { ) } - access(Manage) + access(Host) fun recoverHealth(_ type: AttributeType, _ amount: Int64) { let health = self.borrowHealth() let status = self.borrowStatus() @@ -2112,7 +2126,7 @@ access(all) contract FGameMishal { ) } - access(Manage) + access(Host) fun damageHealth( _ attacks: {AttackType: Int64}, _ penetration: Int64, @@ -2153,10 +2167,12 @@ access(all) contract FGameMishal { 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} // ---- Interface ---- - access(Manage) view fun borrowCultivableAttributes(): auth(Mutate) &Attributes + access(Host) view fun borrowCultivableAttributes(): auth(Mutate) &Attributes // ---- Cultivable Methods, Read ---- @@ -2166,9 +2182,18 @@ access(all) contract FGameMishal { return UInt64(status.potentiality.initial) + self.potentialityObtained - self.potentialityUsed } - // --- Cultivable Methods, Write --- + 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 + } - access(Manage) + // --- Cultivable Methods - Attribute, Write --- + + access(Host) fun gainPotentiality(_ amount: UInt64) { self.potentialityObtained = self.potentialityObtained + amount @@ -2179,16 +2204,7 @@ access(all) contract FGameMishal { ) } - 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 - } - - access(Manage) + access(Host) fun upgradeAttribute(_ type: AttributeType, _ amount: UInt64) { let attributes = self.borrowCultivableAttributes() @@ -2197,10 +2213,8 @@ access(all) contract FGameMishal { assert(self.canUpgradeAttribute(type), message: "Not enough potentiality to upgrade attribute") // consume potentiality = 3 x current attribute value let currentValue = attributes.getValue(type) - let consume = currentValue * 3 - // consume potentiality - self.potentialityUsed = self.potentialityUsed + UInt64(consume) + self.consumePotentiality(UInt64(currentValue * 3)) // upgrade attribute attributes.addValue(type, 1) @@ -2215,6 +2229,74 @@ access(all) contract FGameMishal { uuid: self.uuid ) } + + // --- Cultivable Methods - Ability, Read --- + + access(all) view + fun getCultivationLevel(_ ability: EntryIdentifier): UInt64 { + return self.cultivation[ability.getStringID()] ?? 0 + } + + access(all) + fun getAbilitiesOccupiedAttributes(): Attributes { + let occupied = Attributes(strength: 0, vitality: 0, spirit: 0) + let ownedAbilities = self.borrowAbilities() + + for abilityRef in ownedAbilities { + occupied.addValue(AttributeType.STRENGTH, abilityRef.occupy.strength) + occupied.addValue(AttributeType.VITALITY, abilityRef.occupy.vitality) + occupied.addValue(AttributeType.SPIRIT, abilityRef.occupy.spirit) + } + + return occupied + } + + // --- Cultivable Methods - Ability, Write --- + + access(Host) + fun gainAbility(_ ability: @FungibleEntry) { + pre { + ability.identifier.verify(LibraryCategory.ABILITY): "Ability identifier is invalid" + } + let itemId = ability.identifier.getStringID() + self.cultivation[itemId] = 0 + + let abilityRef = ability.identifier.borrowAbility() ?? panic("Not Exists, Ability: ".concat(itemId)) + + // Consume potentiality = 3 x ability level + self.consumePotentiality(abilityRef.level * 3) + + // Check ability occupied value + } + + access(Host) + 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" + } + // TODO + } + + // --- Cultivable Methods - Potentiality, Write --- + + 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 Pawn resource is refered to as the character in the game. @@ -2238,6 +2320,8 @@ access(all) contract FGameMishal { 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 @@ -2282,6 +2366,7 @@ access(all) contract FGameMishal { self.potentialityUsed = 0 self.potentialityObtained = 0 + self.cultivation = {} self.collection <- create EntryCollection() @@ -2313,7 +2398,7 @@ access(all) contract FGameMishal { return self.borrowCultivableAttributes() } - access(Manage) view + access(Host) view fun borrowCultivableAttributes(): auth(Mutate) &Attributes { return &self.attributes } @@ -2333,17 +2418,17 @@ access(all) contract FGameMishal { return &self.status } - access(Manage) view - fun borrowCollection(): auth(Manage) &EntryCollection { + access(contract) view + fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { return &self.collection } - access(Manage) view + access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { return self.status.borrowWritableSlotsOccupied() } - access(Manage) view + access(Host) view fun borrowHealth(): auth(Mutate) &Attributes { return &self.health } From 581c0aeb3968c0cb5b44cc788a876bc3b6ee80cf Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 18:39:39 +0800 Subject: [PATCH 17/45] feat: enhance FGameMishal contract with new gameplay methods for abilities and items; introduce gainAbility and dropAbility functions, improve item management with updated lootItem and dropItem methods, and streamline equipped items handling for better gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 460 ++++++++++++++++++------------ 1 file changed, 279 insertions(+), 181 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 47a9824..dd0167c 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1441,6 +1441,23 @@ access(all) contract FGameMishal { fun hasAbilities(): Bool { return self.getAbilitiesLength() > 0 } + + // --- Gameplay Methods --- + + access(Host) + fun gainAbility(_ ability: @FungibleEntry) { + pre { + ability.identifier.verify(LibraryCategory.ABILITY): "Ability identifier is invalid" + } + } + + access(Host) + 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 { @@ -1487,158 +1504,6 @@ access(all) contract FGameMishal { } } - access(all) resource interface EquippedItemsCarrier: ItemsCarrier { - access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} - access(all) fun getSlotsAll(): {EquipSlot: UInt8} - - 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 - } - - 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 - } - - access(all) - fun isItemEquippable(_ item: EntryIdentifier): Bool { - if let itemRef = item.borrowItem() { - return self._isItemEquippable(itemRef) - } - return false - } - - // --- Item Gameplay Methods --- - - access(Host) - 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) - } - } - - access(Host) - fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { - if self.hasItemEquipped(item) { - self.unequipItem(item) - } - return <- self.withdraw(item.getStringID(), amount: amount) - } - - access(Host) - 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 - ) - } - - access(Host) - 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 - ) - } - - 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 - } - } - access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { access(contract) view fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection @@ -1704,8 +1569,43 @@ access(all) contract FGameMishal { } } + access(all) resource interface StaticUnitCollectionCarrier: UnitCollectionBaseCarrier { + access(all) let collection: @EntryCollection + + // ---- Implement UnitCollectionBaseCarrier ---- + + access(contract) view + fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { + return &self.collection + } + + // ---- Implement Item Gameplay Methods ---- + + access(Host) + fun lootItem(_ entry: @FungibleEntry) { + self.deposit(entry: <-entry) + } + + access(Host) + fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { + return <- self.withdraw(item.getStringID(), amount: amount) + } + + // ---- Implement Ability Gameplay Methods ---- + + access(Host) + fun gainAbility(_ ability: @FungibleEntry) { + self.deposit(entry: <-ability) + } + + access(Host) + fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry { + return <- self.withdraw(ability.getStringID(), amount: 1.0) + } + } + // The Feature resource is used to define the features of the entry. - access(all) resource Feature: Nameable, OptionalStatusCarrier, UnitCollectionBaseCarrier, EffectsCarrier { + access(all) resource Feature: Nameable, OptionalStatusCarrier, StaticUnitCollectionCarrier, EffectsCarrier { access(all) let name: String access(all) let tags: [String] access(all) let attributes: Attributes? @@ -1763,25 +1663,6 @@ access(all) contract FGameMishal { } } } - - // ---- Implement UnitCollectionBaseCarrier ---- - - access(contract) view - fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { - return &self.collection - } - - // ---- Implement Item Gameplay Methods ---- - - access(Host) - fun lootItem(_ entry: @FungibleEntry) { - self.deposit(entry: <-entry) - } - - access(Host) - fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { - return <- self.withdraw(item.getStringID(), amount: amount) - } } access(all) resource interface FeaturesCarrier { @@ -1835,10 +1716,13 @@ access(all) contract FGameMishal { } } - access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, EquippedItemsCarrier, FeaturesCarrier, ShapeCarrier { + access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, ItemsCarrier, FeaturesCarrier, ShapeCarrier { access(all) view fun borrowSelfAttributes(): &Attributes access(all) view fun borrowSelfDefence(): &Defence access(all) view fun borrowSelfPotentiality(): &Potentiality + access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} + + // ---- Implement ComposableUnitStatusCarrier ---- access(all) fun borrowAttributesElements(): [&Attributes] { @@ -1928,6 +1812,8 @@ access(all) contract FGameMishal { return ret } + // ---- Implement Item equipment info ---- + access(all) fun getSlotsAll(): {EquipSlot: UInt8} { let all: {EquipSlot: UInt8} = {} @@ -1958,9 +1844,134 @@ access(all) contract FGameMishal { return all } + + 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 + } + + 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 + } + + access(all) + fun isItemEquippable(_ item: EntryIdentifier): Bool { + if let itemRef = item.borrowItem() { + return self._isItemEquippable(itemRef) + } + return false + } + + access(Host) + 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 + ) + } + + access(Host) + 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 + ) + } + + 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 + } } - access(all) resource Creature: Nameable, CreatureInterface, UnitCollectionCarrier, BioCarrier { + access(all) resource Creature: Nameable, CreatureInterface, UnitCollectionCarrier, StaticUnitCollectionCarrier, BioCarrier { access(all) let name: String access(all) let tags: [String] access(all) let collection: @EntryCollection @@ -2053,17 +2064,68 @@ access(all) contract FGameMishal { return &self.basePotentiality } - access(contract) view - fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { - return &self.collection - } - access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { return self.status.borrowWritableSlotsOccupied() } } + access(all) resource interface ComposableUnitCollectionCarrier: UnitCollectionCarrier, FeaturesCarrier { + access(all) + fun getFixedAbilities(): [EntryIdentifier] { + let ret: [EntryIdentifier] = [] + let features = self.borrowFeatures() + + let uniqueAbilities: {String: Bool} = {} + for feature in features { + if feature.hasAbilities() { + let abilities = feature.getAbilityIdentifiers() + for ability in abilities { + if uniqueAbilities[ability.getStringID()] == nil { + uniqueAbilities[ability.getStringID()] = true + ret.append(ability) + } + } + } + } + return ret + } + + access(all) + fun getFixedItems(): [EntryIdentifier] { + let ret: [EntryIdentifier] = [] + let features = self.borrowFeatures() + + let uniqueItems: {String: Bool} = {} + for feature in features { + if feature.hasItems() { + let items = feature.borrowItemEntries() + for item in items { + if uniqueItems[item.identifier.getStringID()] == nil { + uniqueItems[item.identifier.getStringID()] = true + ret.append(EntryIdentifier( + library: item.identifier.library, + category: item.identifier.category, + id: item.identifier.id + )) + } + } + } + } + return ret + } + + access(Host) + fun applyFixedAbilities() { + // TODO + } + + access(Host) + fun applyFixedItems() { + // TODO + } + } + // ------------ Player ------------ access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { @@ -2162,7 +2224,7 @@ access(all) contract FGameMishal { } } - access(all) resource interface CultivableUnit: ComposableUnitStatusCarrier, UnitCollectionCarrier { + access(all) resource interface CultivableUnit: CreatureInterface, ComposableUnitCollectionCarrier { // The potentiality used by the character access(all) var potentialityUsed: UInt64 // The potentiality obtained by the character @@ -2267,6 +2329,9 @@ access(all) contract FGameMishal { self.consumePotentiality(abilityRef.level * 3) // Check ability occupied value + // TODO + + self.applyStatus() } access(Host) @@ -2275,6 +2340,37 @@ access(all) contract FGameMishal { result.identifier.getStringID() == ability.getStringID(): "The ID of the withdrawn token must be the same as the requested ID" } // TODO + + self.applyStatus() + } + + // ---- Cultivable Item Gameplay Methods ---- + + access(Host) + 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() + } + + access(Host) + fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { + if self.hasItemEquipped(item) { + self.unequipItem(item) + } + + let ret <- self.withdraw(item.getStringID(), amount: amount) + + self.applyStatus() + + return <- ret } // --- Cultivable Methods - Potentiality, Write --- @@ -2300,7 +2396,7 @@ access(all) contract FGameMishal { } // The Pawn resource is refered to as the character in the game. - access(all) resource Pawn: CreatureInterface, BioCarrier, PlayableUnit, CultivableUnit { + access(all) resource Pawn: CultivableUnit, PlayableUnit, BioCarrier { access(contract) let library: Address // Initial defence(will not be changed after initialization) access(self) let initDefence: Defence @@ -2388,6 +2484,8 @@ access(all) contract FGameMishal { } } + self.applyFixedAbilities() + self.applyFixedItems() self.applyStatus() } From ddeeeb6f2b887781b52a415cb458cb1e9b0f9be2 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 20:19:52 +0800 Subject: [PATCH 18/45] feat: enhance FGameMishal contract with new events and methods for ability cultivation and feature application; introduce PawnAbilityCultivated event, improve applyFeature method, and refine ability management for better gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 209 ++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 69 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index dd0167c..6449c5f 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -40,14 +40,14 @@ access(all) contract FGameMishal { access(all) event CreatureSettingUpdated(_ type: String, _ uuid: UInt64, _ setting: UInt8, _ value: Int64) 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 PawnPotentialityConsumed(_ owner: Address?, _ consume: UInt64, _ usable: UInt64, _ used: UInt64, uuid: UInt64) - // ----- Contract Level Variables ----- // The counter variable for the library items @@ -864,7 +864,12 @@ access(all) contract FGameMishal { access(all) fun borrowPotentialityElements(): [&Potentiality] access(contract) - fun applyStatus() { + 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 @@ -985,25 +990,28 @@ access(all) contract FGameMishal { access(all) let name: String access(all) let tags: [String] access(all) let level: UInt64 - access(all) let occupy: Attributes + access(all) let occupy: AttributeType? access(all) let effects: [String] + access(all) let attributes: Attributes? view init( level: UInt64, name: String, tags: [String], - occupy: Attributes, - effects: [String] + occupy: AttributeType?, + effects: [String], + attributes: Attributes?, ) { self.name = name self.tags = tags self.level = level self.occupy = occupy self.effects = effects + self.attributes = attributes } access(all) view fun borrowAttributes(): &Attributes? { - return &self.occupy as &Attributes + return &self.attributes } } @@ -1159,6 +1167,11 @@ access(all) contract FGameMishal { 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 { @@ -1327,7 +1340,7 @@ access(all) contract FGameMishal { let keys = category == nil ? self.entries.keys : self.getKeysByCategory(category!) for id in keys { if let ref = self.borrowEntryByID(id) { - ret.append(EntryIdentifier(library: ref.identifier.library, category: ref.identifier.category, id: ref.identifier.id)) + ret.append(ref.identifier.clone()) } } return ret @@ -1600,7 +1613,7 @@ access(all) contract FGameMishal { access(Host) fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry { - return <- self.withdraw(ability.getStringID(), amount: 1.0) + return <- self.withdraw(ability.getStringID(), amount: nil) } } @@ -1687,6 +1700,15 @@ access(all) contract FGameMishal { fun hasFeatures(): Bool { return self.getFeaturesLength() > 0 } + + // ---- Implement Feature Gameplay Methods ---- + + access(Host) + fun applyFeature(_ feature: @FungibleEntry) { + pre { + feature.identifier.verify(LibraryCategory.FEATURE): "Feature identifier is invalid" + } + } } access(all) resource interface UnitCollectionCarrier: UnitCollectionBaseCarrier, FeaturesCarrier { @@ -1700,6 +1722,31 @@ access(all) contract FGameMishal { let collection = self.borrowReadonlyCollection() return collection.getEntryIdentifiers(LibraryCategory.FEATURE) } + + // ---- Implement Feature Gameplay Methods ---- + + // This is static, so we don't need to apply it for now + access(Host) + fun applyFeature(_ feature: @FungibleEntry) { + let collection = self.borrowWritableCollection() + // borrow the feature + let featureRef = feature.identifier.borrowFeature() ?? panic("Not Exists, Feature: ".concat(feature.identifier.getStringID())) + + // 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)) + } + + // apply the feature to the collection + collection.deposit(entry: <- feature) + } } access(all) resource interface BioCarrier { @@ -2021,13 +2068,11 @@ access(all) contract FGameMishal { self.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) for featureIdentifier in features { - assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") - self.collection.deposit(entry: <-create FungibleEntry(identifier: featureIdentifier, amount: 1.0)) + self.applyFeature(<-create FungibleEntry(identifier: featureIdentifier, amount: 1.0)) } for abilityIdentifier in abilities { - assert(abilityIdentifier.verify(LibraryCategory.ABILITY), message: "Ability identifier is invalid") - self.collection.deposit(entry: <-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0)) + self.gainAbility(<-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0)) } if items.length > 0 { @@ -2036,12 +2081,12 @@ access(all) contract FGameMishal { assert(itemIdentifier.verify(LibraryCategory.ITEM), message: "Item identifier is invalid") let id = itemIdentifier.getStringID() let amount = itemAmounts[id] ?? 0.0 - self.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) + self.lootItem(<-create FungibleEntry(identifier: itemIdentifier, amount: amount)) } } // apply the status - self.applyStatus() + self.applyStatus(true) } access(all) view @@ -2068,22 +2113,28 @@ access(all) contract FGameMishal { fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { return self.status.borrowWritableSlotsOccupied() } + + // ---- Implement Feature Gameplay Methods ---- + + // This is static, so we don't need to apply it for now + access(Host) + fun applyFeature(_ feature: @FungibleEntry) { + self.collection.deposit(entry: <- feature) + } } - access(all) resource interface ComposableUnitCollectionCarrier: UnitCollectionCarrier, FeaturesCarrier { + access(all) resource interface ComposableUnitCollectionCarrier: UnitCollectionCarrier { access(all) - fun getFixedAbilities(): [EntryIdentifier] { - let ret: [EntryIdentifier] = [] + fun getFixedAbilities(): {String: Bool} { + let ret: {String: Bool} = {} let features = self.borrowFeatures() - let uniqueAbilities: {String: Bool} = {} for feature in features { if feature.hasAbilities() { let abilities = feature.getAbilityIdentifiers() for ability in abilities { - if uniqueAbilities[ability.getStringID()] == nil { - uniqueAbilities[ability.getStringID()] = true - ret.append(ability) + if ret[ability.getStringID()] == nil { + ret[ability.getStringID()] = true } } } @@ -2092,38 +2143,21 @@ access(all) contract FGameMishal { } access(all) - fun getFixedItems(): [EntryIdentifier] { - let ret: [EntryIdentifier] = [] + fun getFixedItems(): {String: UFix64} { + let ret: {String: UFix64} = {} let features = self.borrowFeatures() - let uniqueItems: {String: Bool} = {} for feature in features { if feature.hasItems() { let items = feature.borrowItemEntries() for item in items { - if uniqueItems[item.identifier.getStringID()] == nil { - uniqueItems[item.identifier.getStringID()] = true - ret.append(EntryIdentifier( - library: item.identifier.library, - category: item.identifier.category, - id: item.identifier.id - )) - } + let itemId = item.identifier.getStringID() + ret[itemId] = item.balance + (ret[itemId] ?? 0.0) } } } return ret } - - access(Host) - fun applyFixedAbilities() { - // TODO - } - - access(Host) - fun applyFixedItems() { - // TODO - } } // ------------ Player ------------ @@ -2302,25 +2336,49 @@ access(all) contract FGameMishal { access(all) fun getAbilitiesOccupiedAttributes(): Attributes { let occupied = Attributes(strength: 0, vitality: 0, spirit: 0) - let ownedAbilities = self.borrowAbilities() - - for abilityRef in ownedAbilities { - occupied.addValue(AttributeType.STRENGTH, abilityRef.occupy.strength) - occupied.addValue(AttributeType.VITALITY, abilityRef.occupy.vitality) - occupied.addValue(AttributeType.SPIRIT, abilityRef.occupy.spirit) + 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 --- + access(Host) + 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 + ) + } + access(Host) fun gainAbility(_ ability: @FungibleEntry) { - pre { - ability.identifier.verify(LibraryCategory.ABILITY): "Ability identifier is invalid" - } 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)) @@ -2328,20 +2386,37 @@ access(all) contract FGameMishal { // Consume potentiality = 3 x ability level self.consumePotentiality(abilityRef.level * 3) + let status = self.borrowStatus() // Check ability occupied value - // TODO + 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") + } - self.applyStatus() + // Add ability to collection + let collection = self.borrowWritableCollection() + collection.deposit(entry: <- ability) + + // Apply the ability effects + self.applyStatus(false) } access(Host) 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" - } - // TODO + let itemId = ability.getStringID() + assert(self.borrowEntryByID(itemId) != nil, message: "Ability not exists") + + // Clear cultivation + self.cultivation[itemId] = 0 + + let collection = self.borrowWritableCollection() + let ret <- collection.withdraw(itemId, amount: nil) - self.applyStatus() + self.applyStatus(false) + + return <- ret } // ---- Cultivable Item Gameplay Methods ---- @@ -2357,7 +2432,7 @@ access(all) contract FGameMishal { self.equipItem(itemId) } - self.applyStatus() + self.applyStatus(false) } access(Host) @@ -2368,7 +2443,7 @@ access(all) contract FGameMishal { let ret <- self.withdraw(item.getStringID(), amount: amount) - self.applyStatus() + self.applyStatus(false) return <- ret } @@ -2470,23 +2545,19 @@ access(all) contract FGameMishal { self.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) for featureIdentifier in features { - assert(featureIdentifier.verify(LibraryCategory.FEATURE), message: "Feature identifier is invalid") - self.collection.deposit(entry: <-create FungibleEntry(identifier: featureIdentifier, amount: 1.0)) + self.applyFeature(<-create FungibleEntry(identifier: featureIdentifier, 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.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) + self.lootItem(<-create FungibleEntry(identifier: itemIdentifier, amount: amount)) } } - self.applyFixedAbilities() - self.applyFixedItems() - self.applyStatus() + self.applyStatus(true) } // ---- Interface Implementation ---- From 67ee5b449c704fbabf681a675bf49916c0f0d4b2 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 20:26:14 +0800 Subject: [PATCH 19/45] refactor: update BioCarrier interface in FGameMishal contract; replace bioPrompts with borrowWritableBioPrompts method for improved access control and streamline prompt management --- cadence/contracts/FGameMishal.cdc | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 6449c5f..3bbbda3 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1750,16 +1750,14 @@ access(all) contract FGameMishal { } access(all) resource interface BioCarrier { - access(all) let bioPrompts: [String] + access(Host) view fun borrowWritableBioPrompts(): auth(Mutate) &[String] - access(all) view - fun borrowBioPrompts(): &[String] { - return &self.bioPrompts - } + access(all) view fun borrowBioPrompts(): &[String] { return self.borrowWritableBioPrompts() } access(Host) fun addBioPrompt(_ prompt: String) { - self.bioPrompts.append(prompt) + let prompts = self.borrowWritableBioPrompts() + prompts.append(prompt) } } @@ -2018,7 +2016,7 @@ access(all) contract FGameMishal { } } - access(all) resource Creature: Nameable, CreatureInterface, UnitCollectionCarrier, StaticUnitCollectionCarrier, BioCarrier { + access(all) resource Creature: Nameable, BioCarrier, CreatureInterface, UnitCollectionCarrier, StaticUnitCollectionCarrier { access(all) let name: String access(all) let tags: [String] access(all) let collection: @EntryCollection @@ -2114,12 +2112,9 @@ access(all) contract FGameMishal { return self.status.borrowWritableSlotsOccupied() } - // ---- Implement Feature Gameplay Methods ---- - - // This is static, so we don't need to apply it for now - access(Host) - fun applyFeature(_ feature: @FungibleEntry) { - self.collection.deposit(entry: <- feature) + access(Host) view + fun borrowWritableBioPrompts(): auth(Mutate) &[String] { + return &self.bioPrompts } } @@ -2562,6 +2557,11 @@ access(all) contract FGameMishal { // ---- Interface Implementation ---- + access(Host) view + fun borrowWritableBioPrompts(): auth(Mutate) &[String] { + return &self.bioPrompts + } + access(all) view fun borrowSelfAttributes(): &Attributes { return self.borrowCultivableAttributes() From e077842a9adf8272310d76e84542f6a11747a5ce Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 18 May 2025 20:29:52 +0800 Subject: [PATCH 20/45] refactor: reorganize ComposableUnitCollectionCarrier interface in FGameMishal contract; move getFixedAbilities and getFixedItems methods to CultivableUnit interface for improved structure and clarity --- cadence/contracts/FGameMishal.cdc | 76 +++++++++++++++---------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 3bbbda3..1c3dadc 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -2118,43 +2118,6 @@ access(all) contract FGameMishal { } } - access(all) resource interface ComposableUnitCollectionCarrier: UnitCollectionCarrier { - 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 - } - - 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 - } - } - // ------------ Player ------------ access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { @@ -2253,7 +2216,7 @@ access(all) contract FGameMishal { } } - access(all) resource interface CultivableUnit: CreatureInterface, ComposableUnitCollectionCarrier { + access(all) resource interface CultivableUnit: CreatureInterface, UnitCollectionCarrier { // The potentiality used by the character access(all) var potentialityUsed: UInt64 // The potentiality obtained by the character @@ -2321,6 +2284,43 @@ access(all) contract FGameMishal { ) } + // --- Cultivable Methods - Feature, Read --- + + 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 + } + + 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 + } + // --- Cultivable Methods - Ability, Read --- access(all) view From bdaabc32c7e223fd7ad46c7e4cf9b0f39f3681ee Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 14:33:09 +0800 Subject: [PATCH 21/45] refactor: enhance FGameMishal contract by adding detailed comments to PlayableUnit and CultivableUnit interfaces; improve clarity on health management and cultivation methods for better code understanding --- cadence/contracts/FGameMishal.cdc | 46 ++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 1c3dadc..cd45382 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -2120,17 +2120,21 @@ access(all) contract FGameMishal { // ------------ Player ------------ + // PlayableUnit interface represents a unit that can participate in gameplay and have health-related actions access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { + // Returns a mutable reference to the unit's health attributes access(Host) view fun borrowHealth(): auth(Mutate) &Attributes // ---- 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 { let health = self.borrowHealth() return health.strength <= 0 || health.vitality <= 0 || health.spirit <= 0 } + // Returns true if all health attributes are zero or below, indicating the unit is dead access(all) view fun isDead(): Bool { let health = self.borrowHealth() @@ -2139,6 +2143,7 @@ access(all) contract FGameMishal { // ---- Gameplay Methods, Write --- + // Resets the unit's health to match its current status attributes access(Host) fun resetHealth() { let health = self.borrowHealth() @@ -2157,6 +2162,7 @@ access(all) contract FGameMishal { ) } + // Recovers a specific attribute of health by a given amount, not exceeding the max value access(Host) fun recoverHealth(_ type: AttributeType, _ amount: Int64) { let health = self.borrowHealth() @@ -2180,6 +2186,7 @@ access(all) contract FGameMishal { ) } + // Applies damage to a specific health attribute based on attack and defense values access(Host) fun damageHealth( _ attacks: {AttackType: Int64}, @@ -2216,26 +2223,28 @@ access(all) contract FGameMishal { } } + // CultivableUnit interface represents a unit that can be cultivated (upgraded) by using potentiality access(all) resource interface CultivableUnit: CreatureInterface, 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 of the character + // The cultivation progress for each ability (by string ID) access(all) var cultivation: {String: UInt64} - // ---- Interface ---- - + // Returns a mutable reference to the unit's cultivable attributes access(Host) view fun borrowCultivableAttributes(): auth(Mutate) &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 @@ -2247,6 +2256,7 @@ access(all) contract FGameMishal { // --- Cultivable Methods - Attribute, Write --- + // Increases the amount of potentiality obtained access(Host) fun gainPotentiality(_ amount: UInt64) { self.potentialityObtained = self.potentialityObtained + amount @@ -2258,6 +2268,7 @@ access(all) contract FGameMishal { ) } + // Upgrades a specific attribute by consuming potentiality access(Host) fun upgradeAttribute(_ type: AttributeType, _ amount: UInt64) { let attributes = self.borrowCultivableAttributes() @@ -2286,6 +2297,7 @@ access(all) contract FGameMishal { // --- Cultivable 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} = {} @@ -2304,6 +2316,7 @@ access(all) contract FGameMishal { return ret } + // Returns a map of fixed items (from features) and their amounts access(all) fun getFixedItems(): {String: UFix64} { let ret: {String: UFix64} = {} @@ -2323,11 +2336,13 @@ access(all) contract FGameMishal { // --- 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) @@ -2350,6 +2365,7 @@ access(all) contract FGameMishal { // --- Cultivable Methods - Ability, Write --- + // Increases the cultivation level of an ability by consuming potentiality access(Host) fun cultivateAbility(_ ability: EntryIdentifier, consume: UInt64, abilityUp: UInt64) { let itemId = ability.getStringID() @@ -2369,6 +2385,7 @@ access(all) contract FGameMishal { ) } + // Adds a new ability to the unit, consuming potentiality and updating status access(Host) fun gainAbility(_ ability: @FungibleEntry) { let itemId = ability.identifier.getStringID() @@ -2398,6 +2415,7 @@ access(all) contract FGameMishal { self.applyStatus(false) } + // Removes an ability from the unit and resets its cultivation access(Host) fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry { let itemId = ability.getStringID() @@ -2416,6 +2434,7 @@ access(all) contract FGameMishal { // ---- Cultivable Item Gameplay Methods ---- + // Adds an item to the unit's collection and equips it if possible access(Host) fun lootItem(_ entry: @FungibleEntry) { let itemId = entry.identifier @@ -2430,6 +2449,7 @@ access(all) contract FGameMishal { self.applyStatus(false) } + // Removes an item from the unit's collection, unequipping it if necessary access(Host) fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { if self.hasItemEquipped(item) { @@ -2445,6 +2465,7 @@ access(all) contract FGameMishal { // --- Cultivable Methods - Potentiality, Write --- + // Consumes a specified amount of potentiality for upgrades or actions access(contract) fun consumePotentiality(_ consume: UInt64) { pre { @@ -2465,12 +2486,13 @@ access(all) contract FGameMishal { } } - // The Pawn resource is refered to as the character in the game. + // Pawn resource represents a playable and cultivable character in the game access(all) resource Pawn: CultivableUnit, PlayableUnit, BioCarrier { + // The address of the library this pawn belongs to access(contract) let library: Address - // Initial defence(will not be changed after initialization) + // Initial defence (will not be changed after initialization) access(self) let initDefence: Defence - // Initial potentiality(will not be changed after initialization) + // Initial potentiality (will not be changed after initialization) access(self) let initPotentiality: Potentiality // --- Status Properties --- @@ -2492,7 +2514,7 @@ access(all) contract FGameMishal { // Cultivable attributes, can be upgraded by potentiality access(all) let attributes: Attributes - // The collection of the character + // The collection of the character (items, abilities, etc.) access(all) let collection: @EntryCollection // --- Settings --- @@ -2502,6 +2524,7 @@ access(all) contract FGameMishal { // The bio prompts of the character access(all) let bioPrompts: [String] + // Initializes a new Pawn with the given parameters init( _ library: Address, shape: EntryIdentifier, @@ -2557,46 +2580,55 @@ access(all) contract FGameMishal { // ---- Interface Implementation ---- + // Returns a mutable reference to the bio prompts array access(Host) view fun borrowWritableBioPrompts(): auth(Mutate) &[String] { return &self.bioPrompts } + // Returns a reference to the pawn's cultivable attributes access(all) view fun borrowSelfAttributes(): &Attributes { return self.borrowCultivableAttributes() } + // Returns a mutable reference to the pawn's cultivable attributes access(Host) view fun borrowCultivableAttributes(): auth(Mutate) &Attributes { return &self.attributes } + // Returns a reference to the pawn's initial defence access(all) view fun borrowSelfDefence(): &Defence { return &self.initDefence } + // Returns a reference to the pawn's initial potentiality access(all) view fun borrowSelfPotentiality(): &Potentiality { return &self.initPotentiality } + // Returns a reference to the pawn's status access(all) view fun borrowStatus(): &UnitStatus { return &self.status } + // Returns a mutable reference to the pawn's collection access(contract) view fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { return &self.collection } + // Returns a mutable reference to the slots occupied by equipped items access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { return self.status.borrowWritableSlotsOccupied() } + // Returns a mutable reference to the pawn's health attributes access(Host) view fun borrowHealth(): auth(Mutate) &Attributes { return &self.health From 08fcb0de50e983b8d6dcbe62b58195fc2685402b Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 14:36:42 +0800 Subject: [PATCH 22/45] refactor: enhance FGameMishal contract by adding comprehensive comments to CreatureInterface and Creature resource; improve clarity on attributes, defence, potentiality, and item management methods for better code understanding --- cadence/contracts/FGameMishal.cdc | 94 +++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index cd45382..0bde3ac 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1761,14 +1761,30 @@ access(all) contract FGameMishal { } } + /// The CreatureInterface defines the core interface for a game creature, including status, equipment, and inventory logic. access(all) resource interface CreatureInterface: 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 defence. + /// + /// @return Reference to the base Defence struct. access(all) view fun borrowSelfDefence(): &Defence + /// Returns a reference to the creature's own base potentiality. + /// + /// @return Reference to the base Potentiality struct. access(all) view fun borrowSelfPotentiality(): &Potentiality + /// Returns a mutable reference to the slots occupied by equipped items. + /// + /// @return Mutable reference to the EquipSlot mapping. access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} // ---- 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()] @@ -1805,6 +1821,9 @@ access(all) contract FGameMishal { 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] = [self.borrowSelfDefence()] @@ -1831,6 +1850,9 @@ access(all) contract FGameMishal { 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] = [self.borrowSelfPotentiality()] @@ -1859,6 +1881,9 @@ access(all) contract FGameMishal { // ---- 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} = {} @@ -1890,6 +1915,10 @@ access(all) contract FGameMishal { return all } + /// 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() @@ -1904,6 +1933,9 @@ access(all) contract FGameMishal { 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] = [] @@ -1931,6 +1963,10 @@ access(all) contract FGameMishal { 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() { @@ -1939,6 +1975,9 @@ access(all) contract FGameMishal { 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(Host) fun equipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) @@ -1970,6 +2009,9 @@ access(all) contract FGameMishal { ) } + /// Unequips an item from the creature, updating occupied slots and emitting an event. + /// + /// @param item The EntryIdentifier of the item to unequip. access(Host) fun unequipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) @@ -2000,6 +2042,10 @@ access(all) contract FGameMishal { ) } + /// 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() @@ -2016,23 +2062,41 @@ access(all) contract FGameMishal { } } + /// The Creature resource represents a static, non-cultivable game character with fixed attributes, defence, potentiality, and inventory. access(all) resource Creature: Nameable, BioCarrier, CreatureInterface, UnitCollectionCarrier, StaticUnitCollectionCarrier { + /// 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 character + /// The settings of the creature (e.g., gender, size, etc.). access(all) let settings: {CreatureSettings: Int64} - // Main attributes of the character + /// The base attributes of the creature. access(all) let baseAttributes: Attributes - // The base defence of the character + /// The base defence of the creature. access(all) let baseDefence: Defence - // The potentiality of the character + /// The base potentiality of the creature. access(all) let basePotentiality: Potentiality - // The merged status of the character + /// The merged status of the creature (after applying features, items, etc.). access(all) let status: UnitStatus - // The bio prompts of the character + /// 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], @@ -2087,31 +2151,49 @@ access(all) contract FGameMishal { self.applyStatus(true) } + /// 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 } + /// 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 } + /// Returns a reference to the base defence of the creature. + /// + /// @return Reference to the base Defence struct. access(all) view fun borrowSelfDefence(): &Defence { return &self.baseDefence } + /// Returns a reference to the base potentiality of the creature. + /// + /// @return Reference to the base Potentiality struct. access(all) view fun borrowSelfPotentiality(): &Potentiality { return &self.basePotentiality } + /// Returns a mutable reference to the slots occupied by equipped items. + /// + /// @return Mutable reference to the EquipSlot mapping. access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { return self.status.borrowWritableSlotsOccupied() } + /// Returns a mutable reference to the bio prompts array. + /// + /// @return Mutable reference to the bioPrompts array. access(Host) view fun borrowWritableBioPrompts(): auth(Mutate) &[String] { return &self.bioPrompts From 10ee5c9643ba56d7ff85cb5df8a2b9547f501abf Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 19:16:28 +0800 Subject: [PATCH 23/45] refactor: update FGameMishal contract by renaming CreatureInterface to EquipableCreatureInterface; introduce new methods for managing fixed abilities and items, and enhance EquipableUnit interface for improved gameplay dynamics and item management --- cadence/contracts/FGameMishal.cdc | 184 ++++++++++++++++++------------ 1 file changed, 109 insertions(+), 75 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 0bde3ac..c6fcf4c 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1761,8 +1761,8 @@ access(all) contract FGameMishal { } } - /// The CreatureInterface defines the core interface for a game creature, including status, equipment, and inventory logic. - access(all) resource interface CreatureInterface: ComposableUnitStatusCarrier, AbilitiesCarrier, ItemsCarrier, FeaturesCarrier, ShapeCarrier { + /// 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. @@ -2060,10 +2060,49 @@ access(all) contract FGameMishal { } 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 + } } /// The Creature resource represents a static, non-cultivable game character with fixed attributes, defence, potentiality, and inventory. - access(all) resource Creature: Nameable, BioCarrier, CreatureInterface, UnitCollectionCarrier, StaticUnitCollectionCarrier { + access(all) resource Creature: Nameable, BioCarrier, EquipableCreatureInterface, UnitCollectionCarrier, StaticUnitCollectionCarrier { /// The name of the creature. access(all) let name: String /// The tags associated with the creature. @@ -2200,7 +2239,7 @@ access(all) contract FGameMishal { } } - // ------------ Player ------------ + // ------------ Playable Unit ------------ // PlayableUnit interface represents a unit that can participate in gameplay and have health-related actions access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { @@ -2274,6 +2313,7 @@ access(all) contract FGameMishal { _ attacks: {AttackType: Int64}, _ penetration: Int64, _ type: AttributeType, + _ extraDefence: Defence? ) { let health = self.borrowHealth() let status = self.borrowStatus() @@ -2282,6 +2322,9 @@ access(all) contract FGameMishal { 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 { @@ -2305,8 +2348,41 @@ access(all) contract FGameMishal { } } + access(all) resource interface EquipableUnit: EquipableCreatureInterface { + // ---- Equipable Item Gameplay Methods ---- + + // Adds an item to the unit's collection and equips it if possible + access(Host) + 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) + 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: CreatureInterface, UnitCollectionCarrier { + access(all) resource interface CultivableUnit: EquipableUnit, UnitCollectionCarrier { // The potentiality used by the character access(all) var potentialityUsed: UInt64 // The potentiality obtained by the character @@ -2377,45 +2453,6 @@ access(all) contract FGameMishal { ) } - // --- Cultivable 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 - } - // --- Cultivable Methods - Ability, Read --- // Returns the cultivation level for a specific ability @@ -2514,37 +2551,6 @@ access(all) contract FGameMishal { return <- ret } - // ---- Cultivable Item Gameplay Methods ---- - - // Adds an item to the unit's collection and equips it if possible - access(Host) - 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) - 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 - } - // --- Cultivable Methods - Potentiality, Write --- // Consumes a specified amount of potentiality for upgrades or actions @@ -2568,6 +2574,34 @@ access(all) contract FGameMishal { } } + // CreaturePawn resource represents a playable and cultivable NPC in the game + // access(all) resource CreaturePawn: CreatureInterface, UnitCollectionCarrier, PlayableUnit, BioCarrier { + // // The address of the library this pawn belongs to + // access(contract) let library: Address + + // // --- Status Properties --- + + // // The merged status of the character + // access(all) let status: UnitStatus + // // The health of the character, can be damaged + // access(all) var health: Attributes + + // // --- 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 + // } + // Pawn resource represents a playable and cultivable character in the game access(all) resource Pawn: CultivableUnit, PlayableUnit, BioCarrier { // The address of the library this pawn belongs to From aff575b32e7ba47fc1740d0e6797befaa7be18a4 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 21:25:42 +0800 Subject: [PATCH 24/45] refactor: rename StaticUnitCollectionCarrier to StaticCollectionUnit and update Feature resource to use StaticCollectionUnit; introduce StaticCollectionWithFeatures interface for feature application, and enhance EquipableUnit with applyFeature method for improved gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 93 ++++++++++++------------------- 1 file changed, 36 insertions(+), 57 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index c6fcf4c..6296a7d 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1582,7 +1582,7 @@ access(all) contract FGameMishal { } } - access(all) resource interface StaticUnitCollectionCarrier: UnitCollectionBaseCarrier { + access(all) resource interface StaticCollectionUnit: UnitCollectionBaseCarrier { access(all) let collection: @EntryCollection // ---- Implement UnitCollectionBaseCarrier ---- @@ -1618,7 +1618,7 @@ access(all) contract FGameMishal { } // The Feature resource is used to define the features of the entry. - access(all) resource Feature: Nameable, OptionalStatusCarrier, StaticUnitCollectionCarrier, EffectsCarrier { + access(all) resource Feature: Nameable, OptionalStatusCarrier, StaticCollectionUnit, EffectsCarrier { access(all) let name: String access(all) let tags: [String] access(all) let attributes: Attributes? @@ -1722,31 +1722,6 @@ access(all) contract FGameMishal { let collection = self.borrowReadonlyCollection() return collection.getEntryIdentifiers(LibraryCategory.FEATURE) } - - // ---- Implement Feature Gameplay Methods ---- - - // This is static, so we don't need to apply it for now - access(Host) - fun applyFeature(_ feature: @FungibleEntry) { - let collection = self.borrowWritableCollection() - // borrow the feature - let featureRef = feature.identifier.borrowFeature() ?? panic("Not Exists, Feature: ".concat(feature.identifier.getStringID())) - - // 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)) - } - - // apply the feature to the collection - collection.deposit(entry: <- feature) - } } access(all) resource interface BioCarrier { @@ -2101,8 +2076,16 @@ access(all) contract FGameMishal { } } - /// The Creature resource represents a static, non-cultivable game character with fixed attributes, defence, potentiality, and inventory. - access(all) resource Creature: Nameable, BioCarrier, EquipableCreatureInterface, UnitCollectionCarrier, StaticUnitCollectionCarrier { + // 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) + } + } + + /// The Creature resource represents a static, character template with fixed attributes, defence, potentiality, and inventory. + access(all) resource Creature: Nameable, BioCarrier, EquipableCreatureInterface, UnitCollectionCarrier, StaticCollectionWithFeatures { /// The name of the creature. access(all) let name: String /// The tags associated with the creature. @@ -2349,6 +2332,30 @@ access(all) contract FGameMishal { } 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) + fun applyFeature(_ feature: @FungibleEntry) { + // borrow the feature + let featureRef = feature.identifier.borrowFeature() ?? panic("Not Exists, Feature: ".concat(feature.identifier.getStringID())) + + // 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)) + } + + // apply the feature to the collection + self.deposit(entry: <- feature) + } + // ---- Equipable Item Gameplay Methods ---- // Adds an item to the unit's collection and equips it if possible @@ -2574,34 +2581,6 @@ access(all) contract FGameMishal { } } - // CreaturePawn resource represents a playable and cultivable NPC in the game - // access(all) resource CreaturePawn: CreatureInterface, UnitCollectionCarrier, PlayableUnit, BioCarrier { - // // The address of the library this pawn belongs to - // access(contract) let library: Address - - // // --- Status Properties --- - - // // The merged status of the character - // access(all) let status: UnitStatus - // // The health of the character, can be damaged - // access(all) var health: Attributes - - // // --- 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 - // } - // Pawn resource represents a playable and cultivable character in the game access(all) resource Pawn: CultivableUnit, PlayableUnit, BioCarrier { // The address of the library this pawn belongs to From ab0309922656d2d37a1938019f59bf8371d41874 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 22:14:40 +0800 Subject: [PATCH 25/45] refactor: restructure FGameMishal contract by consolidating resource interfaces and enhancing gameplay methods; introduce EntryCollection resource for better entry management, applyShape method for shape handling, and improve clarity on creature attributes and settings for streamlined gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 843 +++++++++++++++--------------- 1 file changed, 427 insertions(+), 416 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 6296a7d..cab0f48 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -910,243 +910,6 @@ access(all) contract FGameMishal { } } - 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 - } - } - - 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 - } - } - - access(all) resource Ability: AttributeCarrier, 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? - - view init( - level: UInt64, - name: String, - tags: [String], - occupy: AttributeType?, - effects: [String], - attributes: Attributes?, - ) { - self.name = name - self.tags = tags - self.level = level - self.occupy = occupy - self.effects = effects - self.attributes = attributes - } - - access(all) view fun borrowAttributes(): &Attributes? { - return &self.attributes - } - } - - // The CreatureSettingsCarrier resource interface is used to get the settings of the creature. - access(all) resource interface CreatureSettingsCarrier { - access(all) let settings: {CreatureSettings: Int64} - - access(all) view - fun getSetting(_ setting: CreatureSettings): Int64? { - return self.settings[setting] - } - - access(all) view - fun hasSettings(): Bool { - return self.settings.length > 0 - } - - access(Host) - fun updateSetting(_ setting: CreatureSettings, _ value: Int64) { - self.settings[setting] = value - - emit CreatureSettingUpdated( - self.getType().identifier, - self.uuid, - setting.rawValue, - value - ) - } - } - - access(all) resource Shape: CreatureSettingsCarrier, 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 - } - } - - // The ShapeCarrier resource interface is used to get the shape of the creature. - access(all) resource interface ShapeCarrier: CreatureSettingsCarrier { - access(all) let settings: {CreatureSettings: Int64} - - access(all) view fun borrowShape(): &Shape? - - access(all) view - fun hasShape(): Bool { - return self.borrowShape() != nil - } - - access(all) view - fun getGender(): Int64? { - if let gender = self.settings[CreatureSettings.GENDER] { - return gender - } - if let shape = self.borrowShape() { - return shape.getSetting(CreatureSettings.GENDER) - } - return nil - } - - access(all) view - fun getForm(): Int64? { - if let form = self.settings[CreatureSettings.FORM] { - return form - } - if let shape = self.borrowShape() { - return shape.getSetting(CreatureSettings.FORM) - } - return nil - } - - access(all) view - fun getSize(): Int64? { - if let size = self.settings[CreatureSettings.SIZE] { - return size - } - if let shape = self.borrowShape() { - return shape.getSetting(CreatureSettings.SIZE) - } - return nil - } - - access(all) view - fun getMoveSpeed(): Int64? { - if let moveSpeed = self.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? { - if let perceptionRange = self.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? { - if let occupyRange = self.settings[CreatureSettings.OCCUPY_RANGE] { - return occupyRange - } - if let shape = self.borrowShape() { - return shape.getSetting(CreatureSettings.OCCUPY_RANGE) - } - return nil - } - } - // The EntryIdentifier resource is used to identify the entry. access(all) struct EntryIdentifier { access(all) let library: Address @@ -1303,132 +1066,379 @@ access(all) contract FGameMishal { 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 + // 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(Creator) + 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(Creator) + 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) 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 + } + } + + 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 + } + } + + access(all) resource Ability: AttributeCarrier, 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? + + view init( + level: UInt64, + name: String, + tags: [String], + occupy: AttributeType?, + effects: [String], + attributes: Attributes?, + ) { + self.name = name + self.tags = tags + self.level = level + self.occupy = occupy + self.effects = effects + self.attributes = attributes + } + + access(all) view fun borrowAttributes(): &Attributes? { + return &self.attributes + } + } + + // The CreatureSettingsCarrier resource interface is used to get the settings of the creature. + access(all) resource interface CreatureSettingsCarrier { + access(all) let settings: {CreatureSettings: Int64} + + access(all) view + fun getSetting(_ setting: CreatureSettings): Int64? { + return self.settings[setting] + } + + access(all) view + fun hasSettings(): Bool { + return self.settings.length > 0 + } + + access(Host) + fun updateSetting(_ setting: CreatureSettings, _ value: Int64) { + self.settings[setting] = value + + emit CreatureSettingUpdated( + self.getType().identifier, + self.uuid, + setting.rawValue, + value + ) + } + } + + access(all) resource Shape: CreatureSettingsCarrier, 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 } } - // 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 = {} - } + // The ShapeCarrier resource interface is used to get the shape of the creature. + access(all) resource interface ShapeCarrier: CreatureSettingsCarrier, EntryContainer { + access(all) let settings: {CreatureSettings: Int64} - access(all) view fun getLength(): Int { - return self.entries.length - } + access(all) view fun borrowShape(): &Shape? - access(all) view fun getLengthByCategory(_ category: LibraryCategory): Int { - return self.categories[category]?.length ?? 0 + access(all) view + fun hasShape(): Bool { + return self.borrowShape() != nil } - 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()) - } + access(all) view + fun getGender(): Int64? { + if let gender = self.settings[CreatureSettings.GENDER] { + return gender } - 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) - } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.GENDER) } - return ret + return nil } access(all) view - fun getKeysByCategory(_ category: LibraryCategory): [String] { - return self.categories[category] ?? [] + fun getForm(): Int64? { + if let form = self.settings[CreatureSettings.FORM] { + return form + } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.FORM) + } + return nil } access(all) view - fun borrowEntryByID(_ id: String): &FungibleEntry? { - return &self.entries[id] + fun getSize(): Int64? { + if let size = self.settings[CreatureSettings.SIZE] { + return size + } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.SIZE) + } + return nil } - access(Creator) - 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 - ) + access(all) view + fun getMoveSpeed(): Int64? { + if let moveSpeed = self.settings[CreatureSettings.MOVE_SPEED] { + return moveSpeed } - 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 shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.MOVE_SPEED) } + return nil + } - if let oldRef = self.borrowEntryByID(uid) { - oldRef.deposit(from: <- entry) - } else { - self.entries[uid] <-! entry + access(all) view + fun getPerceptionRange(): Int64? { + if let perceptionRange = self.settings[CreatureSettings.PERCEPTION_RANGE] { + return perceptionRange + } + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.PERCEPTION_RANGE) } + return nil } - access(Creator) - 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 - ) + access(all) view + fun getOccupyRange(): Int64? { + if let occupyRange = self.settings[CreatureSettings.OCCUPY_RANGE] { + return occupyRange } - 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!) + if let shape = self.borrowShape() { + return shape.getSetting(CreatureSettings.OCCUPY_RANGE) } + return nil } - access(Host) view - fun borrowEditableEntry(_ id: String): auth(FungibleToken.Withdraw) &FungibleEntry? { - return &self.entries[id] + // --- Gameplay Methods --- + + access(Host) + fun applyShape(_ shape: @FungibleEntry) { + pre { + shape.identifier.verify(LibraryCategory.SHAPE): "Shape identifier is invalid" + } } } @@ -1517,15 +1527,20 @@ access(all) contract FGameMishal { } } - access(all) resource interface UnitCollectionBaseCarrier: AbilitiesCarrier, ItemsCarrier, ShapeCarrier { - access(contract) view fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection - - // --- Implement EntryContainer --- + // This is a resource that can borrow a writable collection + access(all) resource interface CollectionContainer { + access(contract) + view fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection - // Only Creator can borrow the collection - access(all) view fun borrowReadonlyCollection(): &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() @@ -1570,6 +1585,7 @@ access(all) contract FGameMishal { // --- Implement ShapeCarrier --- + // Borrow the shape of the unit access(all) view fun borrowShape(): &Shape? { let collection = self.borrowReadonlyCollection() let shape = collection.getKeysByCategory(LibraryCategory.SHAPE) @@ -1580,9 +1596,27 @@ access(all) contract FGameMishal { } return nil } + + // Apply a shape to the unit + access(Host) + 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) + } } - access(all) resource interface StaticCollectionUnit: UnitCollectionBaseCarrier { + // 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 ---- @@ -1591,7 +1625,10 @@ access(all) contract FGameMishal { fun borrowWritableCollection(): auth(Creator, 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) @@ -1657,28 +1694,25 @@ access(all) contract FGameMishal { self.settings = settings self.collection <- create EntryCollection() - // Set the entries - - // check identifiers + // Set the entries for shape, abilities, items if shape != nil { - assert(shape!.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") - self.collection.deposit(entry: <-create FungibleEntry(identifier: shape!, amount: 1.0)) + self.applyShape(<-create FungibleEntry(identifier: shape!, amount: 1.0)) } for abilityIdentifier in abilities { - self.collection.deposit(entry: <-create FungibleEntry(identifier: abilityIdentifier, amount: 1.0)) + 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.collection.deposit(entry: <-create FungibleEntry(identifier: itemIdentifier, amount: amount)) + self.lootItem(<-create FungibleEntry(identifier: itemIdentifier, amount: amount)) } } } } - access(all) resource interface FeaturesCarrier { + access(all) resource interface FeaturesCarrier: EntryContainer { access(all) view fun getFeaturesLength(): Int access(all) fun getFeatureIdentifiers(): [EntryIdentifier] @@ -1750,10 +1784,6 @@ access(all) contract FGameMishal { /// /// @return Reference to the base Potentiality struct. access(all) view fun borrowSelfPotentiality(): &Potentiality - /// Returns a mutable reference to the slots occupied by equipped items. - /// - /// @return Mutable reference to the EquipSlot mapping. - access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} // ---- Implement ComposableUnitStatusCarrier ---- @@ -1890,6 +1920,14 @@ access(all) contract FGameMishal { return all } + /// Returns a mutable reference to the slots occupied by equipped items. + /// + /// @return Mutable reference to the EquipSlot mapping. + access(Host) 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. @@ -2084,8 +2122,31 @@ access(all) contract FGameMishal { } } + // 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, BioCarrier, EquipableCreatureInterface, UnitCollectionCarrier, StaticCollectionWithFeatures { + access(all) resource Creature: Nameable, BioPromptsUnit, MergableStatusUnit, EquipableCreatureInterface, UnitCollectionCarrier, StaticCollectionWithFeatures { /// The name of the creature. access(all) let name: String /// The tags associated with the creature. @@ -2148,8 +2209,8 @@ access(all) contract FGameMishal { ) self.collection <- create EntryCollection() - assert(shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") - self.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) + // 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)) @@ -2173,14 +2234,6 @@ access(all) contract FGameMishal { self.applyStatus(true) } - /// 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 - } - /// Returns a reference to the base attributes of the creature. /// /// @return Reference to the base Attributes struct. @@ -2204,22 +2257,6 @@ access(all) contract FGameMishal { fun borrowSelfPotentiality(): &Potentiality { return &self.basePotentiality } - - /// Returns a mutable reference to the slots occupied by equipped items. - /// - /// @return Mutable reference to the EquipSlot mapping. - access(Host) view - fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { - return self.status.borrowWritableSlotsOccupied() - } - - /// Returns a mutable reference to the bio prompts array. - /// - /// @return Mutable reference to the bioPrompts array. - access(Host) view - fun borrowWritableBioPrompts(): auth(Mutate) &[String] { - return &self.bioPrompts - } } // ------------ Playable Unit ------------ @@ -2534,8 +2571,7 @@ access(all) contract FGameMishal { } // Add ability to collection - let collection = self.borrowWritableCollection() - collection.deposit(entry: <- ability) + self.deposit(entry: <- ability) // Apply the ability effects self.applyStatus(false) @@ -2550,8 +2586,7 @@ access(all) contract FGameMishal { // Clear cultivation self.cultivation[itemId] = 0 - let collection = self.borrowWritableCollection() - let ret <- collection.withdraw(itemId, amount: nil) + let ret <- self.withdraw(itemId, amount: nil) self.applyStatus(false) @@ -2582,7 +2617,7 @@ access(all) contract FGameMishal { } // Pawn resource represents a playable and cultivable character in the game - access(all) resource Pawn: CultivableUnit, PlayableUnit, BioCarrier { + access(all) resource Pawn: PlayableUnit, CultivableUnit, CollectionContainerUnit, BioPromptsUnit, MergableStatusUnit { // The address of the library this pawn belongs to access(contract) let library: Address // Initial defence (will not be changed after initialization) @@ -2654,8 +2689,8 @@ access(all) contract FGameMishal { self.collection <- create EntryCollection() - assert(shape.verify(LibraryCategory.SHAPE), message: "Shape identifier is invalid") - self.collection.deposit(entry: <-create FungibleEntry(identifier: shape, amount: 1.0)) + // 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)) @@ -2675,24 +2710,12 @@ access(all) contract FGameMishal { // ---- Interface Implementation ---- - // Returns a mutable reference to the bio prompts array - access(Host) view - fun borrowWritableBioPrompts(): auth(Mutate) &[String] { - return &self.bioPrompts - } - // Returns a reference to the pawn's cultivable attributes access(all) view fun borrowSelfAttributes(): &Attributes { return self.borrowCultivableAttributes() } - // Returns a mutable reference to the pawn's cultivable attributes - access(Host) view - fun borrowCultivableAttributes(): auth(Mutate) &Attributes { - return &self.attributes - } - // Returns a reference to the pawn's initial defence access(all) view fun borrowSelfDefence(): &Defence { @@ -2705,22 +2728,10 @@ access(all) contract FGameMishal { return &self.initPotentiality } - // Returns a reference to the pawn's status - access(all) view - fun borrowStatus(): &UnitStatus { - return &self.status - } - - // Returns a mutable reference to the pawn's collection - access(contract) view - fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { - return &self.collection - } - - // Returns a mutable reference to the slots occupied by equipped items + // Returns a mutable reference to the pawn's cultivable attributes access(Host) view - fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { - return self.status.borrowWritableSlotsOccupied() + fun borrowCultivableAttributes(): auth(Mutate) &Attributes { + return &self.attributes } // Returns a mutable reference to the pawn's health attributes From 44c19f3e44db8c3e2066d17e86ac92fbb076c9f1 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 23:22:06 +0800 Subject: [PATCH 26/45] feat: impl Pawn's initialization --- cadence/contracts/FGameMishal.cdc | 168 ++++++++++++++++++++++++------ 1 file changed, 134 insertions(+), 34 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index cab0f48..18dc4b9 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -1303,21 +1303,25 @@ access(all) contract FGameMishal { // The CreatureSettingsCarrier resource interface is used to get the settings of the creature. access(all) resource interface CreatureSettingsCarrier { - access(all) let settings: {CreatureSettings: Int64} + access(contract) view + fun borrowWritableSettings(): auth(Mutate) &{CreatureSettings: Int64} access(all) view fun getSetting(_ setting: CreatureSettings): Int64? { - return self.settings[setting] + let settings = self.borrowWritableSettings() + return settings[setting] } access(all) view fun hasSettings(): Bool { - return self.settings.length > 0 + let settings = self.borrowWritableSettings() + return settings.length > 0 } access(Host) fun updateSetting(_ setting: CreatureSettings, _ value: Int64) { - self.settings[setting] = value + let settings = self.borrowWritableSettings() + settings[setting] = value emit CreatureSettingUpdated( self.getType().identifier, @@ -1328,7 +1332,16 @@ access(all) contract FGameMishal { } } - access(all) resource Shape: CreatureSettingsCarrier, Nameable { + 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} @@ -1357,18 +1370,25 @@ access(all) contract FGameMishal { // The ShapeCarrier resource interface is used to get the shape of the creature. access(all) resource interface ShapeCarrier: CreatureSettingsCarrier, EntryContainer { - access(all) let settings: {CreatureSettings: Int64} + access(all) view fun getShapeIdentifier(): EntryIdentifier? - access(all) view fun borrowShape(): &Shape? + access(all) view + fun borrowShape(): &Shape? { + if let shape = self.getShapeIdentifier() { + return shape.borrowShape() + } + return nil + } access(all) view fun hasShape(): Bool { - return self.borrowShape() != nil + return self.getShapeIdentifier() != nil } access(all) view fun getGender(): Int64? { - if let gender = self.settings[CreatureSettings.GENDER] { + let settings = self.borrowWritableSettings() + if let gender = settings[CreatureSettings.GENDER] { return gender } if let shape = self.borrowShape() { @@ -1379,7 +1399,8 @@ access(all) contract FGameMishal { access(all) view fun getForm(): Int64? { - if let form = self.settings[CreatureSettings.FORM] { + let settings = self.borrowWritableSettings() + if let form = settings[CreatureSettings.FORM] { return form } if let shape = self.borrowShape() { @@ -1390,7 +1411,8 @@ access(all) contract FGameMishal { access(all) view fun getSize(): Int64? { - if let size = self.settings[CreatureSettings.SIZE] { + let settings = self.borrowWritableSettings() + if let size = settings[CreatureSettings.SIZE] { return size } if let shape = self.borrowShape() { @@ -1401,7 +1423,8 @@ access(all) contract FGameMishal { access(all) view fun getMoveSpeed(): Int64? { - if let moveSpeed = self.settings[CreatureSettings.MOVE_SPEED] { + let settings = self.borrowWritableSettings() + if let moveSpeed = settings[CreatureSettings.MOVE_SPEED] { return moveSpeed } if let shape = self.borrowShape() { @@ -1412,7 +1435,8 @@ access(all) contract FGameMishal { access(all) view fun getPerceptionRange(): Int64? { - if let perceptionRange = self.settings[CreatureSettings.PERCEPTION_RANGE] { + let settings = self.borrowWritableSettings() + if let perceptionRange = settings[CreatureSettings.PERCEPTION_RANGE] { return perceptionRange } if let shape = self.borrowShape() { @@ -1423,7 +1447,8 @@ access(all) contract FGameMishal { access(all) view fun getOccupyRange(): Int64? { - if let occupyRange = self.settings[CreatureSettings.OCCUPY_RANGE] { + let settings = self.borrowWritableSettings() + if let occupyRange = settings[CreatureSettings.OCCUPY_RANGE] { return occupyRange } if let shape = self.borrowShape() { @@ -1586,12 +1611,13 @@ access(all) contract FGameMishal { // --- Implement ShapeCarrier --- // Borrow the shape of the unit - access(all) view fun borrowShape(): &Shape? { + 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.borrowShape() + return entry.identifier.clone() } } return nil @@ -1655,7 +1681,7 @@ access(all) contract FGameMishal { } // The Feature resource is used to define the features of the entry. - access(all) resource Feature: Nameable, OptionalStatusCarrier, StaticCollectionUnit, EffectsCarrier { + access(all) resource Feature: Nameable, OptionalStatusCarrier, StaticCollectionUnit, EffectsCarrier, SettingsUnit { access(all) let name: String access(all) let tags: [String] access(all) let attributes: Attributes? @@ -2146,7 +2172,7 @@ access(all) contract FGameMishal { } /// The Creature resource represents a static, character template with fixed attributes, defence, potentiality, and inventory. - access(all) resource Creature: Nameable, BioPromptsUnit, MergableStatusUnit, EquipableCreatureInterface, UnitCollectionCarrier, StaticCollectionWithFeatures { + 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. @@ -2617,7 +2643,7 @@ access(all) contract FGameMishal { } // Pawn resource represents a playable and cultivable character in the game - access(all) resource Pawn: PlayableUnit, CultivableUnit, CollectionContainerUnit, BioPromptsUnit, MergableStatusUnit { + access(all) resource Pawn: PlayableUnit, CultivableUnit, CollectionContainerUnit, BioPromptsUnit, MergableStatusUnit, SettingsUnit { // The address of the library this pawn belongs to access(contract) let library: Address // Initial defence (will not be changed after initialization) @@ -2657,55 +2683,129 @@ access(all) contract FGameMishal { // Initializes a new Pawn with the given parameters init( _ library: Address, - shape: EntryIdentifier, + template: EntryIdentifier?, + shape: EntryIdentifier?, features: [EntryIdentifier], items: [EntryIdentifier], itemAmounts: {String: UFix64}, bioPrompts: [String] ) { self.library = library + self.settings = {} self.bioPrompts = bioPrompts - let lib = FGameMishal.borrowLibrary(library) ?? panic("Library not found") - let initPtt = lib.settings[LibrarySettings.INIT_POTENTIALITY] ?? 36 - let initDef = lib.settings[LibrarySettings.INIT_DEFENCE_VALUE] ?? 0 - let initAttr = lib.settings[LibrarySettings.INIT_ATTRIBUTE_VALUE] ?? 3 + // 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 + } + + // Apply the template if it exists + if template != nil { + let creature = template!.borrowCreature() ?? panic("Not Exists, Creature: ".concat(template!.getStringID())) + + // Extract template's Abilities, Defence, Potentiality + self.initDefence = creature.borrowSelfDefence().copy() as! Defence + self.initPotentiality = creature.borrowSelfPotentiality().copy() as! Potentiality + self.attributes = creature.borrowSelfAttributes().copy() as! Attributes + + // 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 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.INIT_POTENTIALITY] ?? 36 + let initDef = lib.settings[LibrarySettings.INIT_DEFENCE_VALUE] ?? 0 + let initAttr = lib.settings[LibrarySettings.INIT_ATTRIBUTE_VALUE] ?? 3 + + self.initDefence = Defence(physical: initDef, endurance: initDef, resistance: initDef) + self.initPotentiality = Potentiality(initial: initPtt) + self.attributes = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) - self.initDefence = Defence(physical: initDef, endurance: initDef, resistance: initDef) - 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: self.initDefence, potentiality: self.initPotentiality ) - self.health = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) + self.health = Attributes(strength: 0, vitality: 0, spirit: 0) + // 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: shape, amount: 1.0)) + self.applyShape(<-create FungibleEntry(identifier: shapeToApply!, amount: 1.0)) - for featureIdentifier in features { + for featureIdentifier in featuresToApply { self.applyFeature(<-create FungibleEntry(identifier: featureIdentifier, 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 { + 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 = itemAmounts[id] ?? 0.0 + let amount = itemAmountsToApply[id] ?? 0.0 self.lootItem(<-create FungibleEntry(identifier: itemIdentifier, amount: amount)) } } + // Apply the status and reset the health self.applyStatus(true) + self.resetHealth() } // ---- Interface Implementation ---- From f8883d58e9f327630c62f9ae399e8629f73a5508 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 23:46:28 +0800 Subject: [PATCH 27/45] feat: enhance FGameMishal contract by adding ability management for fixed abilities and improving feature application; implement ability extraction from templates and ensure potentiality consumption is conditional on ability type for better gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 52 ++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 18dc4b9..634c69b 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -2403,6 +2403,9 @@ access(all) contract FGameMishal { // 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 { @@ -2414,9 +2417,6 @@ access(all) contract FGameMishal { for itemEntry in itemsFromFeature { self.lootItem(<- create FungibleEntry(identifier: itemEntry.identifier.clone(), amount: itemEntry.balance)) } - - // apply the feature to the collection - self.deposit(entry: <- feature) } // ---- Equipable Item Gameplay Methods ---- @@ -2584,16 +2584,21 @@ access(all) contract FGameMishal { let abilityRef = ability.identifier.borrowAbility() ?? panic("Not Exists, Ability: ".concat(itemId)) - // 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") + // 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 @@ -2688,6 +2693,7 @@ access(all) contract FGameMishal { features: [EntryIdentifier], items: [EntryIdentifier], itemAmounts: {String: UFix64}, + abilities: [EntryIdentifier], bioPrompts: [String] ) { self.library = library @@ -2711,6 +2717,12 @@ access(all) contract FGameMishal { itemAmountsToApply[itemId] = itemAmounts[itemId] ?? 0.0 } + let abilitiesToApply = abilities + let abilitiesDict: {String: Bool} = {} + for ability in abilities { + abilitiesDict[ability.getStringID()] = true + } + // Apply the template if it exists if template != nil { let creature = template!.borrowCreature() ?? panic("Not Exists, Creature: ".concat(template!.getStringID())) @@ -2737,6 +2749,15 @@ access(all) contract FGameMishal { } } + // 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 { @@ -2768,7 +2789,6 @@ access(all) contract FGameMishal { self.initDefence = Defence(physical: initDef, endurance: initDef, resistance: initDef) 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 @@ -2794,6 +2814,10 @@ access(all) contract FGameMishal { 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 { From 6645efe6d128336a145aa8b5f4be04229646d8c5 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 19 May 2025 23:53:06 +0800 Subject: [PATCH 28/45] feat: add CreatureBioPromptAdded event and implement bio prompt addition in FGameMishal contract for enhanced creature management --- cadence/contracts/FGameMishal.cdc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 634c69b..0e20fc8 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -38,6 +38,7 @@ access(all) contract FGameMishal { 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 PawnPotentialityGained(_ owner: Address?, _ amount: UInt64, uuid: UInt64) access(all) event PawnPotentialityConsumed(_ owner: Address?, _ consume: UInt64, _ usable: UInt64, _ used: UInt64, uuid: UInt64) @@ -1793,6 +1794,12 @@ access(all) contract FGameMishal { fun addBioPrompt(_ prompt: String) { let prompts = self.borrowWritableBioPrompts() prompts.append(prompt) + + emit CreatureBioPromptAdded( + self.owner?.address, + uuid: self.uuid, + prompt + ) } } From e5d992b33510639f54d86143eb5b0dbb31a75624 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Tue, 20 May 2025 22:18:51 +0800 Subject: [PATCH 29/45] refactor: update FGameMishal contract by renaming LibrarySettings cases for clarity, enhancing Ability resource with defence attributes, and improving methods for borrowing attributes and defence; streamline initialization logic for better gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 94 +++++++++++++------------------ 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 0e20fc8..1598b7b 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -60,9 +60,8 @@ access(all) contract FGameMishal { // ----- Resources ----- access(all) enum LibrarySettings: UInt8 { - access(all) case INIT_POTENTIALITY - access(all) case INIT_ATTRIBUTE_VALUE - access(all) case INIT_DEFENCE_VALUE + access(all) case DEFAULT_POTENTIALITY + access(all) case DEFAULT_ATTRIBUTE_VALUE } access(all) enum AttributeType: UInt8 { @@ -137,9 +136,8 @@ access(all) contract FGameMishal { init() { self.settings = { - LibrarySettings.INIT_POTENTIALITY: 36, - LibrarySettings.INIT_ATTRIBUTE_VALUE: 3, - LibrarySettings.INIT_DEFENCE_VALUE: 0 + LibrarySettings.DEFAULT_POTENTIALITY: 36, + LibrarySettings.DEFAULT_ATTRIBUTE_VALUE: 3 } self.objects <- {} self.items <- {} @@ -1273,13 +1271,14 @@ access(all) contract FGameMishal { } } - access(all) resource Ability: AttributeCarrier, EffectsCarrier, Nameable { + 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, @@ -1288,6 +1287,7 @@ access(all) contract FGameMishal { occupy: AttributeType?, effects: [String], attributes: Attributes?, + defence: Defence? ) { self.name = name self.tags = tags @@ -1295,11 +1295,16 @@ access(all) contract FGameMishal { 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 + } } // The CreatureSettingsCarrier resource interface is used to get the settings of the creature. @@ -1809,14 +1814,13 @@ access(all) contract FGameMishal { /// /// @return Reference to the base Attributes struct. access(all) view fun borrowSelfAttributes(): &Attributes - /// Returns a reference to the creature's own base defence. - /// - /// @return Reference to the base Defence struct. - access(all) view fun borrowSelfDefence(): &Defence + /// Returns a reference to the creature's own base potentiality. /// /// @return Reference to the base Potentiality struct. - access(all) view fun borrowSelfPotentiality(): &Potentiality + access(all) view fun borrowSelfPotentiality(): &Potentiality? { + return nil + } // ---- Implement ComposableUnitStatusCarrier ---- @@ -1864,7 +1868,7 @@ access(all) contract FGameMishal { /// @return Array of references to all Defence affecting the creature. access(all) fun borrowDefenceElements(): [&Defence] { - let ret: [&Defence] = [self.borrowSelfDefence()] + let ret: [&Defence] = [] // From Features let features = self.borrowFeatures() @@ -1876,6 +1880,15 @@ access(all) contract FGameMishal { } } + // 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 { @@ -1893,7 +1906,11 @@ access(all) contract FGameMishal { /// @return Array of references to all Potentiality affecting the creature. access(all) fun borrowPotentialityElements(): [&Potentiality] { - let ret: [&Potentiality] = [self.borrowSelfPotentiality()] + let ret: [&Potentiality] = [] + + if let selfPotentiality = self.borrowSelfPotentiality() { + ret.append(selfPotentiality) + } // From Features let features = self.borrowFeatures() @@ -2190,10 +2207,6 @@ access(all) contract FGameMishal { access(all) let settings: {CreatureSettings: Int64} /// The base attributes of the creature. access(all) let baseAttributes: Attributes - /// The base defence of the creature. - access(all) let baseDefence: Defence - /// The base potentiality of the creature. - access(all) let basePotentiality: Potentiality /// The merged status of the creature (after applying features, items, etc.). access(all) let status: UnitStatus /// The bio prompts of the creature. @@ -2219,8 +2232,6 @@ access(all) contract FGameMishal { shape: EntryIdentifier, settings: {CreatureSettings: Int64}, attributes: Attributes, - defence: Defence, - potentiality: Potentiality, features: [EntryIdentifier], abilities: [EntryIdentifier], items: [EntryIdentifier], @@ -2232,13 +2243,11 @@ access(all) contract FGameMishal { self.settings = settings self.bioPrompts = bioPrompts self.baseAttributes = attributes - self.baseDefence = defence - self.basePotentiality = potentiality // Initialize the status self.status = UnitStatus( attributes: self.baseAttributes, - defence: self.baseDefence, - potentiality: self.basePotentiality + defence: Defence(physical: 0,endurance: 0,resistance: 0), + potentiality: Potentiality(initial: 0) ) self.collection <- create EntryCollection() @@ -2274,22 +2283,6 @@ access(all) contract FGameMishal { fun borrowSelfAttributes(): &Attributes { return &self.baseAttributes } - - /// Returns a reference to the base defence of the creature. - /// - /// @return Reference to the base Defence struct. - access(all) view - fun borrowSelfDefence(): &Defence { - return &self.baseDefence - } - - /// Returns a reference to the base potentiality of the creature. - /// - /// @return Reference to the base Potentiality struct. - access(all) view - fun borrowSelfPotentiality(): &Potentiality { - return &self.basePotentiality - } } // ------------ Playable Unit ------------ @@ -2658,8 +2651,6 @@ access(all) contract FGameMishal { access(all) resource Pawn: PlayableUnit, CultivableUnit, CollectionContainerUnit, BioPromptsUnit, MergableStatusUnit, SettingsUnit { // The address of the library this pawn belongs to access(contract) let library: Address - // Initial defence (will not be changed after initialization) - access(self) let initDefence: Defence // Initial potentiality (will not be changed after initialization) access(self) let initPotentiality: Potentiality @@ -2734,10 +2725,9 @@ access(all) contract FGameMishal { if template != nil { let creature = template!.borrowCreature() ?? panic("Not Exists, Creature: ".concat(template!.getStringID())) - // Extract template's Abilities, Defence, Potentiality - self.initDefence = creature.borrowSelfDefence().copy() as! Defence - self.initPotentiality = creature.borrowSelfPotentiality().copy() as! Potentiality + // 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 { @@ -2789,11 +2779,9 @@ access(all) contract FGameMishal { // 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.INIT_POTENTIALITY] ?? 36 - let initDef = lib.settings[LibrarySettings.INIT_DEFENCE_VALUE] ?? 0 - let initAttr = lib.settings[LibrarySettings.INIT_ATTRIBUTE_VALUE] ?? 3 + let initPtt = lib.settings[LibrarySettings.DEFAULT_POTENTIALITY] ?? 36 + let initAttr = lib.settings[LibrarySettings.DEFAULT_ATTRIBUTE_VALUE] ?? 3 - self.initDefence = Defence(physical: initDef, endurance: initDef, resistance: initDef) self.initPotentiality = Potentiality(initial: initPtt) self.attributes = Attributes(strength: initAttr, vitality: initAttr, spirit: initAttr) } @@ -2801,7 +2789,7 @@ access(all) contract FGameMishal { // Initialize the status and health, but it will be reset later self.status = UnitStatus( attributes: self.attributes, - defence: self.initDefence, + defence: Defence(physical: 0,endurance: 0,resistance: 0), potentiality: self.initPotentiality ) self.health = Attributes(strength: 0, vitality: 0, spirit: 0) @@ -2847,12 +2835,6 @@ access(all) contract FGameMishal { return self.borrowCultivableAttributes() } - // Returns a reference to the pawn's initial defence - access(all) view - fun borrowSelfDefence(): &Defence { - return &self.initDefence - } - // Returns a reference to the pawn's initial potentiality access(all) view fun borrowSelfPotentiality(): &Potentiality { From 39a3093d43588481a281f5fbb6051d04a74eebfe Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Tue, 20 May 2025 22:57:45 +0800 Subject: [PATCH 30/45] refactor: remove Creator entitlement from FGameMishal contract, update access control for deposit and withdraw functions to contract level, and introduce new methods for creating objects, items, abilities, shapes, features, and creature templates to enhance gameplay dynamics and resource management --- cadence/contracts/FGameMishal.cdc | 119 +++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 1598b7b..0969831 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -16,8 +16,6 @@ import "FixesHeartbeat" access(all) contract FGameMishal { // Entitlements for the Editor role access(all) entitlement Editor; - // Entitlements for the Creator role (Like the creator of the game) - access(all) entitlement Creator; // 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) @@ -411,6 +409,8 @@ access(all) contract FGameMishal { return nil } + // -------- Private Functions -------- + access(self) view fun borrowNameToUIDDictionary(_ category: LibraryCategory): auth(Mutate) &{String: UInt64} { return &self.nameToUID[category] as auth(Mutate) &{String: UInt64}? @@ -432,8 +432,6 @@ access(all) contract FGameMishal { return [] } - // -------- Private Functions -------- - access(self) fun borrowNamable(_ category: LibraryCategory, _ uuid: UInt64): &{Nameable}? { switch category { @@ -490,6 +488,12 @@ access(all) contract FGameMishal { } } + // ------------ Host Resources ------------ + + access(all) resource HostOperator { + // TODO: Implement the HostOperator resource + } + // ------------ Library Entities ------------ access(all) struct interface Copyable { @@ -1058,10 +1062,10 @@ access(all) contract FGameMishal { access(all) view fun borrowEntryByID(_ id: String): &FungibleEntry? - access(Creator) + access(contract) fun deposit(entry: @FungibleEntry) - access(Creator) + access(contract) fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry } @@ -1130,7 +1134,7 @@ access(all) contract FGameMishal { return &self.entries[id] } - access(Creator) + access(contract) fun deposit(entry: @FungibleEntry) { pre { emit EntryDeposited( @@ -1156,7 +1160,7 @@ access(all) contract FGameMishal { } } - access(Creator) + 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" @@ -1220,6 +1224,17 @@ access(all) contract FGameMishal { } } + // 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] @@ -1271,6 +1286,22 @@ access(all) contract FGameMishal { } } + // 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] @@ -1307,6 +1338,20 @@ access(all) contract FGameMishal { } } + // 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 @@ -1374,6 +1419,20 @@ access(all) contract FGameMishal { } } + // 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? @@ -1561,7 +1620,7 @@ access(all) contract FGameMishal { // This is a resource that can borrow a writable collection access(all) resource interface CollectionContainer { access(contract) - view fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection + view fun borrowWritableCollection(): auth(Host) &EntryCollection access(all) view fun borrowReadonlyCollection(): &EntryCollection { @@ -1578,12 +1637,12 @@ access(all) contract FGameMishal { return collection.borrowEntryByID(id) } - access(Creator) + access(contract) fun deposit(entry: @FungibleEntry) { self.borrowWritableCollection().deposit(entry: <-entry) } - access(Creator) + access(contract) fun withdraw(_ id: String, amount: UFix64?): @FungibleEntry { return <- self.borrowWritableCollection().withdraw(id, amount: amount) } @@ -1654,7 +1713,7 @@ access(all) contract FGameMishal { // ---- Implement UnitCollectionBaseCarrier ---- access(contract) view - fun borrowWritableCollection(): auth(Creator, Host) &EntryCollection { + fun borrowWritableCollection(): auth(Host) &EntryCollection { return &self.collection } } @@ -1744,6 +1803,25 @@ access(all) contract FGameMishal { } } + // 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] @@ -2285,6 +2363,23 @@ access(all) contract FGameMishal { } } + // 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 ------------ // PlayableUnit interface represents a unit that can participate in gameplay and have health-related actions From 0c32489176715b22e900c3796bda8aee8b9b3b7f Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Thu, 22 May 2025 18:27:17 +0800 Subject: [PATCH 31/45] feat: add new view functions to FGameMishal contract for retrieving tag UIDs, entry lengths, and all entry UIDs by category, enhancing library management capabilities --- cadence/contracts/FGameMishal.cdc | 63 ++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 0969831..c52b07c 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -331,6 +331,58 @@ access(all) contract FGameMishal { // -------- 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] @@ -409,6 +461,8 @@ access(all) contract FGameMishal { return nil } + // -------- Restricted Functions -------- + // -------- Private Functions -------- access(self) view @@ -423,15 +477,6 @@ access(all) contract FGameMishal { ?? panic("Tag to UID dictionary not found") } - access(self) view - fun getTagUIDs(_ category: LibraryCategory, _ tag: String): [UInt64] { - let tagToUIDs = self.borrowTagToUIDsDictionary(category) - if let uids = tagToUIDs[tag] { - return *uids - } - return [] - } - access(self) fun borrowNamable(_ category: LibraryCategory, _ uuid: UInt64): &{Nameable}? { switch category { From f0ee442e44fa03eb3c5818a1f0d9e5b6aef24c30 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 15:26:33 +0800 Subject: [PATCH 32/45] feat: add new writable borrow functions in FGameMishal contract for objects, items, abilities, shapes, features, and creatures to enhance resource management capabilities --- cadence/contracts/FGameMishal.cdc | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index c52b07c..9097a13 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -463,6 +463,42 @@ access(all) contract FGameMishal { // -------- Restricted Functions -------- + // Borrow a writable object by uuid + access(contract) view + fun borrowWritableObject(_ uuid: UInt64): auth(Mutate) &Object? { + return &self.objects[uuid] + } + + // Borrow a writable item by uuid + access(contract) view + fun borrowWritableItem(_ uuid: UInt64): auth(Mutate) &Item? { + return &self.items[uuid] + } + + // Borrow a writable ability by uuid + access(contract) view + fun borrowWritableAbility(_ uuid: UInt64): auth(Mutate) &Ability? { + return &self.abilities[uuid] + } + + // Borrow a writable shape by uuid + access(contract) view + fun borrowWritableShape(_ uuid: UInt64): auth(Mutate) &Shape? { + return &self.shapes[uuid] + } + + // Borrow a writable feature by uuid + access(contract) view + fun borrowWritableFeature(_ uuid: UInt64): auth(Mutate) &Feature? { + return &self.features[uuid] + } + + // Borrow a writable creature by uuid + access(contract) view + fun borrowWritableCreature(_ uuid: UInt64): auth(Mutate) &Creature? { + return &self.creatures[uuid] + } + // -------- Private Functions -------- access(self) view From 27ca54fc898afb3f1276e2cc8e31283c436e7196 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 17:13:09 +0800 Subject: [PATCH 33/45] refactor: update access control in FGameMishal contract by changing authorization from Mutate to Editor for various borrow functions and methods, enhancing security and clarity in resource management --- cadence/contracts/FGameMishal.cdc | 89 ++++++++++++++++--------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 9097a13..eebc951 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -465,37 +465,37 @@ access(all) contract FGameMishal { // Borrow a writable object by uuid access(contract) view - fun borrowWritableObject(_ uuid: UInt64): auth(Mutate) &Object? { + 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(Mutate) &Item? { + 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(Mutate) &Ability? { + 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(Mutate) &Shape? { + 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(Mutate) &Feature? { + 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(Mutate) &Creature? { + fun borrowWritableCreature(_ uuid: UInt64): auth(Editor) &Creature? { return &self.creatures[uuid] } @@ -569,12 +569,6 @@ access(all) contract FGameMishal { } } - // ------------ Host Resources ------------ - - access(all) resource HostOperator { - // TODO: Implement the HostOperator resource - } - // ------------ Library Entities ------------ access(all) struct interface Copyable { @@ -630,7 +624,7 @@ access(all) contract FGameMishal { } /// This method will not check if the value is negative - access(Host) + access(contract) fun setValue(_ type: AttributeType, _ value: Int64) { switch type { case AttributeType.STRENGTH: @@ -645,7 +639,7 @@ access(all) contract FGameMishal { } // For this method, the final value cannot be negative - access(Host) + access(contract) fun addValue(_ type: AttributeType, _ value: Int64) { post { self.strength >= 0: "Strength cannot be negative" @@ -718,7 +712,7 @@ access(all) contract FGameMishal { } // This method will not check if the value is negative - access(Host) + access(contract) fun setValue(_ type: DefenceType, _ value: Int64) { switch type { case DefenceType.PHYSICAL: @@ -814,12 +808,12 @@ access(all) contract FGameMishal { return self.value != nil } - access(Host) + access(Editor) fun setValue(value: UFix64) { self.value = value } - access(Host) + access(Editor) fun addValue(value: UFix64) { post { (self.value ?? 0.0) >= 0.0: "Value cannot be negative" @@ -836,12 +830,12 @@ access(all) contract FGameMishal { return self.effects.length > 0 } - access(Host) + access(Editor) fun addEffect(effect: String) { self.effects.append(effect) } - access(Host) + access(Editor) fun removeEffect(effect: String) { if let index = self.effects.firstIndex(of: effect) { let _ = self.effects.remove(at: index) @@ -1273,7 +1267,7 @@ access(all) contract FGameMishal { } } - access(Host) view + access(Host | Editor) view fun borrowEditableEntry(_ id: String): auth(FungibleToken.Withdraw) &FungibleEntry? { return &self.entries[id] } @@ -1450,7 +1444,7 @@ access(all) contract FGameMishal { return settings.length > 0 } - access(Host) + access(Host | Editor) fun updateSetting(_ setting: CreatureSettings, _ value: Int64) { let settings = self.borrowWritableSettings() settings[setting] = value @@ -1605,7 +1599,7 @@ access(all) contract FGameMishal { // --- Gameplay Methods --- - access(Host) + access(Host | Editor) fun applyShape(_ shape: @FungibleEntry) { pre { shape.identifier.verify(LibraryCategory.SHAPE): "Shape identifier is invalid" @@ -1638,14 +1632,14 @@ access(all) contract FGameMishal { // --- Gameplay Methods --- - access(Host) + access(Host | Editor) fun gainAbility(_ ability: @FungibleEntry) { pre { ability.identifier.verify(LibraryCategory.ABILITY): "Ability identifier is invalid" } } - access(Host) + 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" @@ -1682,14 +1676,14 @@ access(all) contract FGameMishal { // --- Item Gameplay Methods --- - access(Host) + access(Host | Editor) fun lootItem(_ entry: @FungibleEntry) { pre { entry.identifier.verify(LibraryCategory.ITEM): "Entry is not an item" } } - access(Host) + 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" @@ -1770,7 +1764,7 @@ access(all) contract FGameMishal { } // Apply a shape to the unit - access(Host) + access(Host | Editor) fun applyShape(_ shape: @FungibleEntry) { let collection = self.borrowReadonlyCollection() let shapes = collection.getKeysByCategory(LibraryCategory.SHAPE) @@ -1803,24 +1797,24 @@ access(all) contract FGameMishal { access(all) resource interface StaticCollectionUnit: UnitCollectionBaseCarrier, CollectionContainerUnit { // ---- Implement Item Gameplay Methods ---- - access(Host) + access(Host | Editor) fun lootItem(_ entry: @FungibleEntry) { self.deposit(entry: <-entry) } - access(Host) + access(Host | Editor) fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { return <- self.withdraw(item.getStringID(), amount: amount) } // ---- Implement Ability Gameplay Methods ---- - access(Host) + access(Host | Editor) fun gainAbility(_ ability: @FungibleEntry) { self.deposit(entry: <-ability) } - access(Host) + access(Host | Editor) fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry { return <- self.withdraw(ability.getStringID(), amount: nil) } @@ -1928,7 +1922,7 @@ access(all) contract FGameMishal { // ---- Implement Feature Gameplay Methods ---- - access(Host) + access(Host | Editor) fun applyFeature(_ feature: @FungibleEntry) { pre { feature.identifier.verify(LibraryCategory.FEATURE): "Feature identifier is invalid" @@ -1950,11 +1944,12 @@ access(all) contract FGameMishal { } access(all) resource interface BioCarrier { - access(Host) view fun borrowWritableBioPrompts(): auth(Mutate) &[String] + access(Host | Editor) view + fun borrowWritableBioPrompts(): auth(Mutate) &[String] access(all) view fun borrowBioPrompts(): &[String] { return self.borrowWritableBioPrompts() } - access(Host) + access(Host | Editor) fun addBioPrompt(_ prompt: String) { let prompts = self.borrowWritableBioPrompts() prompts.append(prompt) @@ -2132,7 +2127,8 @@ access(all) contract FGameMishal { /// Returns a mutable reference to the slots occupied by equipped items. /// /// @return Mutable reference to the EquipSlot mapping. - access(Host) view fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { + access(Host | Editor) view + fun borrowSlotsOccupied(): auth(Mutate) &{EquipSlot: [String]} { let status = self.borrowStatus() return status.borrowWritableSlotsOccupied() } @@ -2200,7 +2196,7 @@ access(all) contract FGameMishal { /// Equips an item to the creature, updating occupied slots and emitting an event. /// /// @param item The EntryIdentifier of the item to equip. - access(Host) + access(Host | Editor) fun equipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) let itemId = item.getStringID() @@ -2234,7 +2230,7 @@ access(all) contract FGameMishal { /// Unequips an item from the creature, updating occupied slots and emitting an event. /// /// @param item The EntryIdentifier of the item to unequip. - access(Host) + access(Host | Editor) fun unequipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) let itemId = item.getStringID() @@ -2466,7 +2462,8 @@ access(all) contract FGameMishal { // PlayableUnit interface represents a unit that can participate in gameplay and have health-related actions access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { // Returns a mutable reference to the unit's health attributes - access(Host) view fun borrowHealth(): auth(Mutate) &Attributes + access(Host) view + fun borrowHealth(): auth(Mutate) &Attributes // ---- Gameplay Methods, Read --- @@ -2574,7 +2571,7 @@ access(all) contract FGameMishal { // ---- Implement Feature Gameplay Methods ---- // This is static, so we don't need to apply it for now - access(Host) + access(Host | Editor) fun applyFeature(_ feature: @FungibleEntry) { // borrow the feature let featureRef = feature.identifier.borrowFeature() ?? panic("Not Exists, Feature: ".concat(feature.identifier.getStringID())) @@ -2598,7 +2595,7 @@ access(all) contract FGameMishal { // ---- Equipable Item Gameplay Methods ---- // Adds an item to the unit's collection and equips it if possible - access(Host) + access(Host | Editor) fun lootItem(_ entry: @FungibleEntry) { let itemId = entry.identifier let itemRef = itemId.borrowItem() ?? panic("Not Exists, Item: ".concat(itemId.getStringID())) @@ -2613,7 +2610,7 @@ access(all) contract FGameMishal { } // Removes an item from the unit's collection, unequipping it if necessary - access(Host) + access(Host | Editor) fun dropItem(_ item: EntryIdentifier, _ amount: UFix64?): @FungibleEntry { if self.hasItemEquipped(item) { self.unequipItem(item) @@ -2751,7 +2748,7 @@ access(all) contract FGameMishal { } // Adds a new ability to the unit, consuming potentiality and updating status - access(Host) + access(Host | Editor) fun gainAbility(_ ability: @FungibleEntry) { let itemId = ability.identifier.getStringID() assert(self.borrowEntryByID(itemId) == nil, message: "Ability already exists") @@ -2785,7 +2782,7 @@ access(all) contract FGameMishal { } // Removes an ability from the unit and resets its cultivation - access(Host) + access(Host | Editor) fun dropAbility(_ ability: EntryIdentifier): @FungibleEntry { let itemId = ability.getStringID() assert(self.borrowEntryByID(itemId) != nil, message: "Ability not exists") @@ -3030,6 +3027,12 @@ access(all) contract FGameMishal { } } + // ------------ Host Resources ------------ + + access(all) resource HostOperator { + // TODO: Implement the HostOperator resource + } + // ---- Public Functions ---- access(all) From b977aeb218677d3cf3ebc8a2fe19505609f35cbb Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 20:24:33 +0800 Subject: [PATCH 34/45] feat: enhance FGameMishal contract by adding PlayerPocket and HostBoard resources, implementing pawn management functions, and introducing new events for pawn actions to improve gameplay dynamics and resource handling --- cadence/contracts/FGameMishal.cdc | 347 +++++++++++++++++++++++++++++- 1 file changed, 343 insertions(+), 4 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index eebc951..d5c123a 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -18,8 +18,6 @@ access(all) contract FGameMishal { 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; // Not used for now // ----- Events ----- @@ -38,6 +36,10 @@ access(all) contract FGameMishal { 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 PawnDeployed(_ library: Address, _ owner: Address, _ pawnUuid: UInt64, _ host: Address, _ boardUuid: UInt64) + access(all) event PawnRemoved(_ library: Address, _ owner: Address, _ pawnUuid: UInt64, _ host: 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) @@ -55,6 +57,12 @@ access(all) contract FGameMishal { access(all) let libraryStoragePath: StoragePath access(all) let libraryPublicPath: PublicPath + access(all) let pocketStoragePath: StoragePath + access(all) let pocketPublicPath: PublicPath + + access(all) let hostOperatorStoragePath: StoragePath + access(all) let hostOperatorPublicPath: PublicPath + // ----- Resources ----- access(all) enum LibrarySettings: UInt8 { @@ -2824,6 +2832,8 @@ access(all) contract FGameMishal { access(all) resource Pawn: PlayableUnit, 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 @@ -2894,6 +2904,9 @@ access(all) contract FGameMishal { 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())) @@ -3027,19 +3040,338 @@ access(all) contract FGameMishal { } } - // ------------ Host Resources ------------ + // ------------ Host and Player Resources ------------ + + access(all) resource interface PawnSpawner { + 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 + ) + } + } + + // 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 + } + + // Returns a reference to the pawn with the given ID + access(all) view + fun borrowPawn(_ id: UInt64): &Pawn? { + return self.borrowWritablePawn(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, + ) + } + + // Returns a mutable reference to the pawn with the given ID + access(contract) view + fun borrowWritablePawn(_ id: UInt64): auth(Host) &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 borrowWritablePawn(): auth(Host) &Pawn? { + if let pocketRef = FGameMishal.borrowPlayerPocket(self.owner) { + return pocketRef.borrowWritablePawn(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]} + + init() { + self.participants = {} + self.deadUnits = [] + self.leftUnits = [] + } + + 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 [] + } + + // 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.borrowWritablePawn(id) + } + return nil + } + + // Inserts a pawn to the participant's pocket + access(Host) + fun insertPawn(_ address: Address, pawn: @Pawn) { + let pocketRef = FGameMishal.borrowPlayerPocket(address) + ?? panic("Player pocket not found") + let pawnId = pawn.uuid + let library = pawn.library + + let participants = self.borrowAndEnsureParticipants(address) + assert(!participants.contains(pawnId), message: "Pawn already exists") + + participants.append(pawnId) + pocketRef.addPawn(<- pawn) + + emit PawnDeployed( + library, + address, + pawnId, + self.owner?.address ?? panic("Host not exists"), + self.uuid, + ) + } + + // Removes a dead pawn from the host board + access(Host) + fun removeDeadPawn(_ address: Address, _ id: UInt64) { + if let pawn = PawnIdentifier(address, id).borrowWritablePawn() { + // ensure pawn is dead + assert(pawn.isDead(), 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).borrowWritablePawn() { + // ensure pawn is alive + assert(!pawn.isDead(), message: "Pawn should be alive") + + self.removePawn(pawn) + return + } + panic("Pawn not found") + } + + // ---- 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 pawn.isDead() { + self.deadUnits.append(PawnIdentifier(pawnOwner, pawnId)) + } else { + self.leftUnits.append(PawnIdentifier(pawnOwner, pawnId)) + } + + emit PawnRemoved( + pawn.library, + pawnOwner, + pawn.uuid, + self.owner?.address ?? panic("Host not exists"), + self.uuid, + ) + } + } + + access(self) + fun borrowAndEnsureParticipants(_ address: Address): auth(Mutate) &[UInt64] { + if let participants = &self.participants[address] as auth(Mutate) &[UInt64]? { + return participants + } else { + self.participants[address] = [] + return &self.participants[address]! + } + } + } + + // Creates a new host board + access(all) + fun createBoard(): @HostBoard { + return <- create HostBoard() + } + + // 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 { - // TODO: Implement the HostOperator resource + access(contract) + let boards: {UInt64: Capability} + access(contract) + let activeBoards: [UInt64] + + view init() { + self.boards = {} + self.activeBoards = [] + } + + access(all) view + fun getActiveBoardsLength(): Int { + return self.activeBoards.length + } + + // access(Host) + // fun openBoard() + } + + // 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 @@ -3047,6 +3379,7 @@ access(all) contract FGameMishal { .borrow() } + // Returns a reference to the most popular library access(all) view fun borrowPopularLibrary(): &Library? { // Borrow the libraray with the highest item count @@ -3071,5 +3404,11 @@ access(all) contract FGameMishal { 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"))! } } From 5b108a284cec89ac55d5104daaa5de974800e900 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 21:13:09 +0800 Subject: [PATCH 35/45] feat: add HostBoard management functions and events in FGameMishal contract to enhance board handling and gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 59 +++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index d5c123a..5b8cbf0 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -49,6 +49,9 @@ access(all) contract FGameMishal { 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 HostBoardOpened(_ host: Address, _ uuid: UInt64) + access(all) event HostBoardClosed(_ host: Address, _ uuid: UInt64) + // ----- Contract Level Variables ----- // The counter variable for the library items @@ -3328,7 +3331,7 @@ access(all) contract FGameMishal { // 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(all) resource HostOperator: PawnSpawner { access(contract) let boards: {UInt64: Capability} access(contract) @@ -3344,8 +3347,58 @@ access(all) contract FGameMishal { return self.activeBoards.length } - // access(Host) - // fun openBoard() + access(all) view + fun getActiveBoardIDs(): [UInt64] { + return self.activeBoards + } + + access(all) view + fun borrowBoard(_ uuid: UInt64): &HostBoard? { + return self.borrowWritableBoard(uuid) + } + + access(Host) view + fun borrowWritableBoard(_ uuid: UInt64): auth(Host) &HostBoard? { + if let cap = self.boards[uuid] { + return cap.borrow() + } + return nil + } + + access(Host) + fun openBoard(_ cap: Capability) { + pre { + cap.borrow() != nil: "Board's capability is not valid" + } + let board = cap.borrow()! + let uuid = board.uuid + + assert(self.boards[uuid] == nil, message: "Board has already added") + assert(self.activeBoards.contains(uuid), message: "Board is not active") + + self.boards[uuid] = cap + self.activeBoards.append(uuid) + + emit HostBoardOpened(self.owner?.address ?? panic("Host not exists"), uuid) + } + + access(Host) + fun closeBoard(_ uuid: UInt64) { + pre { + self.activeBoards.contains(uuid): "Board is not active" + self.boards[uuid] == nil: "Board has already added" + } + assert(self.boards[uuid] != nil, message: "Board not found") + let board = self.boards[uuid]!.borrow() + ?? panic("Board not found") + + assert(!board.isActive(), message: "Board should be inactive") + + let _cap = self.boards.remove(key: uuid) + let _ = self.activeBoards.remove(at: self.activeBoards.firstIndex(of: uuid)!) + + emit HostBoardClosed(self.owner?.address ?? panic("Host not exists"), uuid) + } } // Creates a new host operator From 553aae4617aafd208bcfbf49a986ce45cfa6f7fb Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 21:14:30 +0800 Subject: [PATCH 36/45] chore: add .gitkeep files for boardgame directories in cadence scripts and transactions to maintain directory structure --- cadence/scripts/boardgame/.gitkeep | 0 cadence/transactions/boardgame/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 cadence/scripts/boardgame/.gitkeep create mode 100644 cadence/transactions/boardgame/.gitkeep diff --git a/cadence/scripts/boardgame/.gitkeep b/cadence/scripts/boardgame/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cadence/transactions/boardgame/.gitkeep b/cadence/transactions/boardgame/.gitkeep new file mode 100644 index 0000000..e69de29 From b9a47bf829f5fde2907f6cb3a58229d588fa2b31 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 22:43:20 +0800 Subject: [PATCH 37/45] feat: enhance FGameMishal contract by adding Player entitlement and updating access control for upgradeAttribute and cultivateAbility functions to include Player role, improving gameplay dynamics and player interaction --- cadence/contracts/FGameMishal.cdc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 5b8cbf0..ef129f8 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -18,6 +18,8 @@ access(all) contract FGameMishal { 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 ----- @@ -2681,7 +2683,7 @@ access(all) contract FGameMishal { } // Upgrades a specific attribute by consuming potentiality - access(Host) + access(Host | Player) fun upgradeAttribute(_ type: AttributeType, _ amount: UInt64) { let attributes = self.borrowCultivableAttributes() @@ -2739,7 +2741,7 @@ access(all) contract FGameMishal { // --- Cultivable Methods - Ability, Write --- // Increases the cultivation level of an ability by consuming potentiality - access(Host) + access(Host | Player) fun cultivateAbility(_ ability: EntryIdentifier, consume: UInt64, abilityUp: UInt64) { let itemId = ability.getStringID() assert(self.borrowEntryByID(itemId) != nil, message: "Ability already exists") @@ -3099,6 +3101,11 @@ access(all) contract FGameMishal { return self.borrowWritablePawn(id) } + access(Player) view + fun borrowEditablePawn(_ id: UInt64): auth(Player) &Pawn? { + return self.borrowWritablePawn(id) + } + // Deploys a pawn to the player's pocket access(contract) fun addPawn(_ pawn: @Pawn) { @@ -3117,7 +3124,7 @@ access(all) contract FGameMishal { // Returns a mutable reference to the pawn with the given ID access(contract) view - fun borrowWritablePawn(_ id: UInt64): auth(Host) &Pawn? { + fun borrowWritablePawn(_ id: UInt64): auth(Host, Player) &Pawn? { return &self.owned[id] } } From f44b88f0abf6f59d99a2229efd750017045208ae Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 22:57:35 +0800 Subject: [PATCH 38/45] feat: add new pawn management functions in FGameMishal contract, including createPawn and storePawn, to enhance gameplay dynamics and player interaction --- cadence/contracts/FGameMishal.cdc | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index ef129f8..da03614 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -3048,6 +3048,7 @@ access(all) contract FGameMishal { // ------------ Host and Player Resources ------------ access(all) resource interface PawnSpawner { + // Creates a new pawn access(Host) fun createPawn( _ library: Address, @@ -3070,6 +3071,14 @@ access(all) contract FGameMishal { bioPrompts: bioPrompts ) } + + // Stores a pawn in the player's pocket + access(Host) + fun storePawn(_ player: Address, _ pawn: @Pawn) { + 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 @@ -3246,22 +3255,21 @@ access(all) contract FGameMishal { // Inserts a pawn to the participant's pocket access(Host) - fun insertPawn(_ address: Address, pawn: @Pawn) { + fun insertPawn(_ address: Address, _ uuid: UInt64) { let pocketRef = FGameMishal.borrowPlayerPocket(address) ?? panic("Player pocket not found") - let pawnId = pawn.uuid - let library = pawn.library + let pawn = pocketRef.borrowWritablePawn(uuid) + ?? panic("Pawn not found") let participants = self.borrowAndEnsureParticipants(address) - assert(!participants.contains(pawnId), message: "Pawn already exists") + assert(!participants.contains(uuid), message: "Pawn already exists") - participants.append(pawnId) - pocketRef.addPawn(<- pawn) + participants.append(uuid) emit PawnDeployed( - library, + pawn.library, address, - pawnId, + uuid, self.owner?.address ?? panic("Host not exists"), self.uuid, ) From 396bdd6b606bbaf2f01d1bf07b751b2da28fbf87 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Fri, 23 May 2025 22:58:20 +0800 Subject: [PATCH 39/45] feat: add pawn status check in FGameMishal contract to ensure pawns are not stunned before adding to participants, enhancing game logic and player experience --- cadence/contracts/FGameMishal.cdc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index da03614..4480a6e 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -3263,6 +3263,8 @@ access(all) contract FGameMishal { let participants = self.borrowAndEnsureParticipants(address) assert(!participants.contains(uuid), message: "Pawn already exists") + // ensure pawn is alive + assert(!pawn.isStunned(), message: "Pawn should not be stunned") participants.append(uuid) From 830458a1cd801ae961200d425da1bd1cb1ac513e Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Tue, 27 May 2025 22:42:04 +0800 Subject: [PATCH 40/45] refactor: update access control in FGameMishal contract by adding Player role to equipItem and unequipItem functions, and rename borrowWritablePawn to borrowFullWritablePawn for improved clarity and functionality --- cadence/contracts/FGameMishal.cdc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 4480a6e..63d5f41 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -2209,7 +2209,7 @@ access(all) contract FGameMishal { /// Equips an item to the creature, updating occupied slots and emitting an event. /// /// @param item The EntryIdentifier of the item to equip. - access(Host | Editor) + access(Editor | Host | Player) fun equipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) let itemId = item.getStringID() @@ -2243,7 +2243,7 @@ access(all) contract FGameMishal { /// Unequips an item from the creature, updating occupied slots and emitting an event. /// /// @param item The EntryIdentifier of the item to unequip. - access(Host | Editor) + access(Editor | Host | Player) fun unequipItem(_ item: EntryIdentifier) { let itemRef = item.borrowItem() ?? panic("Not Exists, Item: ".concat(item.getStringID())) let itemId = item.getStringID() @@ -3107,12 +3107,12 @@ access(all) contract FGameMishal { // Returns a reference to the pawn with the given ID access(all) view fun borrowPawn(_ id: UInt64): &Pawn? { - return self.borrowWritablePawn(id) + return self.borrowFullWritablePawn(id) } access(Player) view fun borrowEditablePawn(_ id: UInt64): auth(Player) &Pawn? { - return self.borrowWritablePawn(id) + return self.borrowFullWritablePawn(id) } // Deploys a pawn to the player's pocket @@ -3133,7 +3133,7 @@ access(all) contract FGameMishal { // Returns a mutable reference to the pawn with the given ID access(contract) view - fun borrowWritablePawn(_ id: UInt64): auth(Host, Player) &Pawn? { + fun borrowFullWritablePawn(_ id: UInt64): auth(Host, Player) &Pawn? { return &self.owned[id] } } @@ -3174,9 +3174,9 @@ access(all) contract FGameMishal { // Returns a mutable reference to the pawn access(contract) view - fun borrowWritablePawn(): auth(Host) &Pawn? { + fun borrowFullWritablePawn(): auth(Host) &Pawn? { if let pocketRef = FGameMishal.borrowPlayerPocket(self.owner) { - return pocketRef.borrowWritablePawn(self.id) + return pocketRef.borrowFullWritablePawn(self.id) } return nil } @@ -3248,7 +3248,7 @@ access(all) contract FGameMishal { access(Host) view fun borrowWritableParticipantPawn(_ address: Address, _ id: UInt64): auth(Host) &Pawn? { if let pocketRef = FGameMishal.borrowPlayerPocket(address) { - return pocketRef.borrowWritablePawn(id) + return pocketRef.borrowFullWritablePawn(id) } return nil } @@ -3258,7 +3258,7 @@ access(all) contract FGameMishal { fun insertPawn(_ address: Address, _ uuid: UInt64) { let pocketRef = FGameMishal.borrowPlayerPocket(address) ?? panic("Player pocket not found") - let pawn = pocketRef.borrowWritablePawn(uuid) + let pawn = pocketRef.borrowFullWritablePawn(uuid) ?? panic("Pawn not found") let participants = self.borrowAndEnsureParticipants(address) @@ -3280,7 +3280,7 @@ access(all) contract FGameMishal { // Removes a dead pawn from the host board access(Host) fun removeDeadPawn(_ address: Address, _ id: UInt64) { - if let pawn = PawnIdentifier(address, id).borrowWritablePawn() { + if let pawn = PawnIdentifier(address, id).borrowFullWritablePawn() { // ensure pawn is dead assert(pawn.isDead(), message: "Pawn is not dead") @@ -3293,7 +3293,7 @@ access(all) contract FGameMishal { // Removes an alive pawn from the host board access(Host) fun removeAlivePawn(_ address: Address, _ id: UInt64) { - if let pawn = PawnIdentifier(address, id).borrowWritablePawn() { + if let pawn = PawnIdentifier(address, id).borrowFullWritablePawn() { // ensure pawn is alive assert(!pawn.isDead(), message: "Pawn should be alive") From 10862f7d88686b2bf36d435f4362ddff25107853 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Wed, 28 May 2025 20:00:55 +0800 Subject: [PATCH 41/45] feat: add new events and functions in FGameMishal contract for managing pawn positions and host boards, enhancing gameplay dynamics and resource handling --- cadence/contracts/FGameMishal.cdc | 163 +++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 5 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 63d5f41..089513b 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -51,6 +51,10 @@ access(all) contract FGameMishal { 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(_ host: Address, _ uuid: UInt64) + access(all) event HostOperatorSpawnerUpdated(_ host: Address, _ uuid: UInt64, _ pawnSpawner: Address, _ pawnSpawnerUuid: UInt64) access(all) event HostBoardOpened(_ host: Address, _ uuid: UInt64) access(all) event HostBoardClosed(_ host: Address, _ uuid: UInt64) @@ -65,6 +69,8 @@ access(all) contract FGameMishal { 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 @@ -2833,6 +2839,48 @@ access(all) contract FGameMishal { } } + // The Positino attachment for PlayableUnit, 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 PlayableUnit { + // Board Information + // The board that the unit is stored in + access(all) var board: Address + // The uuid of the board that the unit is stored in + access(all) var boardUuid: UInt64 + + // The x coordinate of the unit + access(all) var x: Int32 + // The y coordinate of the unit + access(all) var y: Int32 + + init(_ board: Address, _ boardUuid: UInt64) { + self.board = board + self.boardUuid = boardUuid + + self.x = 0 + self.y = 0 + } + + // Sets the position of the unit + access(Host) + fun setPosition(_ x: Int32, _ y: Int32) { + self.x = x + self.y = y + + emit PawnPositionUpdated( + self.board, + self.boardUuid, + unit: base.uuid, + x: self.x, + y: self.y, + ) + } + } + + access(all) resource interface PositionableUnit { + } + // Pawn resource represents a playable and cultivable character in the game access(all) resource Pawn: PlayableUnit, CultivableUnit, CollectionContainerUnit, BioPromptsUnit, MergableStatusUnit, SettingsUnit { // The address of the library this pawn belongs to @@ -3048,6 +3096,10 @@ access(all) contract FGameMishal { // ------------ 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( @@ -3075,6 +3127,9 @@ access(all) contract FGameMishal { // 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) @@ -3303,6 +3358,14 @@ access(all) contract FGameMishal { 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) @@ -3340,23 +3403,88 @@ access(all) contract FGameMishal { } } - // Creates a new host board - access(all) - fun createBoard(): @HostBoard { - return <- create HostBoard() + 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) + } + + // 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 + + 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? { + return self.borrowWritableBoard(self.id) + } + + access(contract) view + fun borrowWritableBoard(_ uuid: UInt64): auth(Host) &HostBoard? { + if let container = self.borrowBoardContainer() { + if let board = container.borrowBoard(self.id) { + return board.borrowInternalWritableBoard() + } + } + 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: PawnSpawner { + access(all) resource HostOperator { access(contract) let boards: {UInt64: Capability} access(contract) let activeBoards: [UInt64] + access(contract) + var pawnSpawner: Capability? view init() { self.boards = {} self.activeBoards = [] + self.pawnSpawner = nil } access(all) view @@ -3374,6 +3502,29 @@ access(all) contract FGameMishal { return self.borrowWritableBoard(uuid) } + 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) view + fun borrowPawnSpawner(): auth(Host) &{PawnSpawner}? { + if let spwaner = self.pawnSpawner?.borrow() { + return spwaner + } + return nil + } + access(Host) view fun borrowWritableBoard(_ uuid: UInt64): auth(Host) &HostBoard? { if let cap = self.boards[uuid] { @@ -3480,5 +3631,7 @@ access(all) contract FGameMishal { self.hostOperatorStoragePath = StoragePath(identifier: identifier.concat("_HostOperator"))! self.hostOperatorPublicPath = PublicPath(identifier: identifier.concat("_HostOperator"))! + + self.hostBoardContainerPublicPath = PublicPath(identifier: identifier.concat("_HostBoardContainer"))! } } From e92bd74141875cee2a5ecbc6be313caf357e84be Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Wed, 28 May 2025 22:15:03 +0800 Subject: [PATCH 42/45] feat: enhance FGameMishal contract by introducing new pawn attachments, updating event names for clarity, and refining health management, improving gameplay mechanics and resource interactions --- cadence/contracts/FGameMishal.cdc | 499 ++++++++++++++++++++++-------- 1 file changed, 368 insertions(+), 131 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 089513b..580d02a 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -39,8 +39,8 @@ access(all) contract FGameMishal { 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 PawnDeployed(_ library: Address, _ owner: Address, _ pawnUuid: UInt64, _ host: Address, _ boardUuid: UInt64) - access(all) event PawnRemoved(_ library: Address, _ owner: Address, _ pawnUuid: UInt64, _ host: Address, _ boardUuid: 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) @@ -53,10 +53,10 @@ access(all) contract FGameMishal { access(all) event PawnPositionUpdated(_ board: Address, _ boardUuid: UInt64, unit: UInt64, x: Int32, y: Int32) - access(all) event HostBoardCreated(_ host: Address, _ uuid: UInt64) + 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 HostBoardOpened(_ host: Address, _ uuid: UInt64) - access(all) event HostBoardClosed(_ host: Address, _ uuid: UInt64) // ----- Contract Level Variables ----- @@ -960,7 +960,7 @@ access(all) contract FGameMishal { access(all) fun borrowDefenceElements(): [&Defence] access(all) fun borrowPotentialityElements(): [&Potentiality] - access(contract) + 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 { @@ -2478,26 +2478,30 @@ access(all) contract FGameMishal { // ------------ Playable Unit ------------ - // PlayableUnit interface represents a unit that can participate in gameplay and have health-related actions - access(all) resource interface PlayableUnit: ComposableUnitStatusCarrier { - // Returns a mutable reference to the unit's health attributes - access(Host) view - fun borrowHealth(): auth(Mutate) &Attributes + 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 { - let health = self.borrowHealth() - return health.strength <= 0 || health.vitality <= 0 || health.spirit <= 0 + 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 { - let health = self.borrowHealth() - return health.strength <= 0 && health.vitality <= 0 && health.spirit <= 0 + return self.info.strength <= 0 && self.info.vitality <= 0 && self.info.spirit <= 0 } // ---- Gameplay Methods, Write --- @@ -2505,43 +2509,41 @@ access(all) contract FGameMishal { // Resets the unit's health to match its current status attributes access(Host) fun resetHealth() { - let health = self.borrowHealth() - let status = self.borrowStatus() + let status = base.borrowStatus() - health.setValue(AttributeType.STRENGTH, status.attributes.strength) - health.setValue(AttributeType.VITALITY, status.attributes.vitality) - health.setValue(AttributeType.SPIRIT, status.attributes.spirit) + 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( - self.owner?.address ?? panic("Owner not found"), - health.strength, - health.vitality, - health.spirit, - uuid: self.uuid + 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 health = self.borrowHealth() - let status = self.borrowStatus() + let status = base.borrowStatus() let maxAttr = status.attributes.getValue(type) - let currentAttr = health.getValue(type) + let currentAttr = self.info.getValue(type) var recoverAmount = amount if currentAttr + amount > maxAttr { recoverAmount = maxAttr - currentAttr } - health.setValue(type, currentAttr + recoverAmount) + self.info.setValue(type, currentAttr + recoverAmount) emit PawnHealthRecovered( - self.owner?.address ?? panic("Owner not found"), + base.owner?.address ?? panic("Owner not found"), type.rawValue, recoverAmount, - uuid: self.uuid + uuid: base.uuid ) } @@ -2553,8 +2555,7 @@ access(all) contract FGameMishal { _ type: AttributeType, _ extraDefence: Defence? ) { - let health = self.borrowHealth() - let status = self.borrowStatus() + let status = base.borrowStatus() var biggestDamage: Int64 = 0 for attackType in attacks.keys { @@ -2575,17 +2576,33 @@ access(all) contract FGameMishal { } } - health.addValue(type, -1 * biggestDamage) + self.info.addValue(type, -1 * biggestDamage) emit PawnHealthDamaged( - self.owner?.address ?? panic("Owner not found"), + base.owner?.address ?? panic("Owner not found"), type.rawValue, biggestDamage, - uuid: self.uuid + 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 ---- @@ -2653,7 +2670,7 @@ access(all) contract FGameMishal { access(all) var cultivation: {String: UInt64} // Returns a mutable reference to the unit's cultivable attributes - access(Host) view fun borrowCultivableAttributes(): auth(Mutate) &Attributes + access(Host) view fun borrowCultivableAttributes(): &Attributes // ---- Cultivable Methods, Read ---- @@ -2839,50 +2856,111 @@ access(all) contract FGameMishal { } } - // The Positino attachment for PlayableUnit, it is used to store the position of the unit for game board + // 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 PlayableUnit { + attachment PositionXY for ComposableUnitStatusCarrier { // Board Information - // The board that the unit is stored in - access(all) var board: Address - // The uuid of the board that the unit is stored in - access(all) var boardUuid: UInt64 - - // The x coordinate of the unit - access(all) var x: Int32 - // The y coordinate of the unit - access(all) var y: Int32 + access(all) let board: BoardIdentifier + // The position of the unit + access(all) let point: PointXY init(_ board: Address, _ boardUuid: UInt64) { - self.board = board - self.boardUuid = boardUuid + self.board = BoardIdentifier(board, boardUuid) - self.x = 0 - self.y = 0 + self.point = PointXY(0, 0) } // Sets the position of the unit access(Host) fun setPosition(_ x: Int32, _ y: Int32) { - self.x = x - self.y = y + self.point.set(x, y) emit PawnPositionUpdated( - self.board, - self.boardUuid, + self.board.container, + self.board.id, unit: base.uuid, - x: self.x, - y: self.y, + 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 + } } - access(all) resource interface PositionableUnit { + // 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, CultivableUnit, CollectionContainerUnit, BioPromptsUnit, MergableStatusUnit, SettingsUnit { + 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? @@ -2894,8 +2972,6 @@ access(all) contract FGameMishal { // The merged status of the character access(all) let status: UnitStatus - // The health of the character, can be damaged - access(all) var health: Attributes // --- Cultivable Property --- @@ -3031,7 +3107,6 @@ access(all) contract FGameMishal { defence: Defence(physical: 0,endurance: 0,resistance: 0), potentiality: self.initPotentiality ) - self.health = Attributes(strength: 0, vitality: 0, spirit: 0) // Initialize the cultivable properties self.potentialityUsed = 0 @@ -3063,7 +3138,6 @@ access(all) contract FGameMishal { // Apply the status and reset the health self.applyStatus(true) - self.resetHealth() } // ---- Interface Implementation ---- @@ -3085,12 +3159,6 @@ access(all) contract FGameMishal { fun borrowCultivableAttributes(): auth(Mutate) &Attributes { return &self.attributes } - - // Returns a mutable reference to the pawn's health attributes - access(Host) view - fun borrowHealth(): auth(Mutate) &Attributes { - return &self.health - } } // ------------ Host and Player Resources ------------ @@ -3159,6 +3227,13 @@ access(all) contract FGameMishal { 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? { @@ -3186,6 +3261,60 @@ access(all) contract FGameMishal { ) } + // 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? { @@ -3246,11 +3375,15 @@ access(all) contract FGameMishal { 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 @@ -3299,6 +3432,44 @@ access(all) contract FGameMishal { 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? { @@ -3318,26 +3489,19 @@ access(all) contract FGameMishal { let participants = self.borrowAndEnsureParticipants(address) assert(!participants.contains(uuid), message: "Pawn already exists") - // ensure pawn is alive - assert(!pawn.isStunned(), message: "Pawn should not be stunned") participants.append(uuid) - - emit PawnDeployed( - pawn.library, - address, - uuid, - self.owner?.address ?? panic("Host not exists"), - self.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(pawn.isDead(), message: "Pawn is not dead") + assert(health?.isDead() ?? false, message: "Pawn is not dead") self.removePawn(pawn) return @@ -3349,8 +3513,9 @@ access(all) contract FGameMishal { access(Host) fun removeAlivePawn(_ address: Address, _ id: UInt64) { if let pawn = PawnIdentifier(address, id).borrowFullWritablePawn() { + let health = pawn.borrowHealth() // ensure pawn is alive - assert(!pawn.isDead(), message: "Pawn should be alive") + assert(!(health?.isDead() ?? false), message: "Pawn should be alive") self.removePawn(pawn) return @@ -3376,31 +3541,62 @@ access(all) contract FGameMishal { if let index = participants.firstIndex(of: pawnId) { let _ = participants.remove(at: index) - if pawn.isDead() { - self.deadUnits.append(PawnIdentifier(pawnOwner, pawnId)) - } else { - self.leftUnits.append(PawnIdentifier(pawnOwner, pawnId)) + if let health = pawn.borrowHealth() { + if health.isDead() { + self.deadUnits.append(PawnIdentifier(pawnOwner, pawnId)) + } else { + self.leftUnits.append(PawnIdentifier(pawnOwner, pawnId)) + } } - emit PawnRemoved( - pawn.library, - pawnOwner, - pawn.uuid, - self.owner?.address ?? panic("Host not exists"), - self.uuid, - ) + 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.participants[address] as 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 { @@ -3437,7 +3633,10 @@ access(all) contract FGameMishal { let container: Address let id: UInt64 - init(_ container: Address, _ id: UInt64) { + view init( + _ container: Address, + _ id: UInt64, + ) { self.container = container self.id = id } @@ -3457,11 +3656,11 @@ access(all) contract FGameMishal { access(all) view fun borrowBoard(): &HostBoard? { - return self.borrowWritableBoard(self.id) + return self.borrowWritableBoard() } access(contract) view - fun borrowWritableBoard(_ uuid: UInt64): auth(Host) &HostBoard? { + fun borrowWritableBoard(): auth(Host) &HostBoard? { if let container = self.borrowBoardContainer() { if let board = container.borrowBoard(self.id) { return board.borrowInternalWritableBoard() @@ -3475,9 +3674,9 @@ access(all) contract FGameMishal { // This resource is stored in the host's account access(all) resource HostOperator { access(contract) - let boards: {UInt64: Capability} + let boards: {Address: [UInt64]} access(contract) - let activeBoards: [UInt64] + let activeBoards: [BoardIdentifier] access(contract) var pawnSpawner: Capability? @@ -3493,15 +3692,11 @@ access(all) contract FGameMishal { } access(all) view - fun getActiveBoardIDs(): [UInt64] { + fun getActiveBoards(): [BoardIdentifier] { return self.activeBoards } - access(all) view - fun borrowBoard(_ uuid: UInt64): &HostBoard? { - return self.borrowWritableBoard(uuid) - } - + // Updates the pawn spawner access(Host) fun updatePawnSpawner(_ pawnSpawner: Capability) { pre { @@ -3517,6 +3712,7 @@ access(all) contract FGameMishal { ) } + // Returns a mutable reference to the pawn spawner access(Host) view fun borrowPawnSpawner(): auth(Host) &{PawnSpawner}? { if let spwaner = self.pawnSpawner?.borrow() { @@ -3525,47 +3721,88 @@ access(all) contract FGameMishal { return nil } + // Returns a mutable reference to the board with the given container and id access(Host) view - fun borrowWritableBoard(_ uuid: UInt64): auth(Host) &HostBoard? { - if let cap = self.boards[uuid] { - return cap.borrow() - } - return nil + fun borrowWritableBoard(_ container: Address, _ id: UInt64): auth(Host) &HostBoard? { + return self.borrowInternalWritableBoard(container, id) } + // Opens a board access(Host) - fun openBoard(_ cap: Capability) { - pre { - cap.borrow() != nil: "Board's capability is not valid" + fun openBoard(_ container: Address, _ id: UInt64) { + let boardIdentifier = BoardIdentifier(container, id) + let board = boardIdentifier.borrowWritableBoard() + ?? panic("Board not found") + + if self.boards[container] == nil { + self.boards[container] = [] } - let board = cap.borrow()! - let uuid = board.uuid + let ids = self.borrowBoardContainerIds(container) + ?? panic("Board container not found") + assert(!ids.contains(id), message: "Board has already added") - assert(self.boards[uuid] == nil, message: "Board has already added") - assert(self.activeBoards.contains(uuid), message: "Board is not active") + ids.append(id) + self.activeBoards.append(boardIdentifier) - self.boards[uuid] = cap - self.activeBoards.append(uuid) + // open the board + board.onOpening() - emit HostBoardOpened(self.owner?.address ?? panic("Host not exists"), uuid) + emit HostBoardOpened(self.owner?.address ?? panic("Host not exists"), container, id) } + // Closes a board access(Host) - fun closeBoard(_ uuid: UInt64) { - pre { - self.activeBoards.contains(uuid): "Board is not active" - self.boards[uuid] == nil: "Board has already added" - } - assert(self.boards[uuid] != nil, message: "Board not found") - let board = self.boards[uuid]!.borrow() + fun closeBoard(_ container: Address, _ id: UInt64) { + let boardIdentifier = BoardIdentifier(container, id) + let board = boardIdentifier.borrowWritableBoard() ?? panic("Board not found") - assert(!board.isActive(), message: "Board should be inactive") + 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() - let _cap = self.boards.remove(key: uuid) - let _ = self.activeBoards.remove(at: self.activeBoards.firstIndex(of: uuid)!) + emit HostBoardClosed(self.owner?.address ?? panic("Host not exists"), container, id) + } - emit HostBoardClosed(self.owner?.address ?? panic("Host not exists"), uuid) + // 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) { + return BoardIdentifier(container, id).borrowWritableBoard() + } + } + 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 } } From 5094e9ee355eb9aab894abe1f411dc046e6d1b51 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Wed, 28 May 2025 22:41:08 +0800 Subject: [PATCH 43/45] feat: add new event and functions in FGameMishal contract for managing board containers, enhancing board handling and gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 57 +++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 580d02a..2f3adf6 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -56,7 +56,9 @@ access(all) contract FGameMishal { 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) // ----- Contract Level Variables ----- @@ -3606,6 +3608,9 @@ access(all) contract FGameMishal { 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? @@ -3656,15 +3661,8 @@ access(all) contract FGameMishal { access(all) view fun borrowBoard(): &HostBoard? { - return self.borrowWritableBoard() - } - - access(contract) view - fun borrowWritableBoard(): auth(Host) &HostBoard? { if let container = self.borrowBoardContainer() { - if let board = container.borrowBoard(self.id) { - return board.borrowInternalWritableBoard() - } + return container.borrowBoard(self.id) } return nil } @@ -3679,11 +3677,14 @@ access(all) contract FGameMishal { 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 @@ -3712,6 +3713,23 @@ access(all) contract FGameMishal { ) } + 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, + ) + } + // Returns a mutable reference to the pawn spawner access(Host) view fun borrowPawnSpawner(): auth(Host) &{PawnSpawner}? { @@ -3730,8 +3748,9 @@ access(all) contract FGameMishal { // Opens a board access(Host) fun openBoard(_ container: Address, _ id: UInt64) { - let boardIdentifier = BoardIdentifier(container, id) - let board = boardIdentifier.borrowWritableBoard() + let containerRef = self.borrowBoardContainer(container) + ?? panic("Board container not found") + let board = containerRef.borrowWritableBoard(id) ?? panic("Board not found") if self.boards[container] == nil { @@ -3742,7 +3761,7 @@ access(all) contract FGameMishal { assert(!ids.contains(id), message: "Board has already added") ids.append(id) - self.activeBoards.append(boardIdentifier) + self.activeBoards.append(BoardIdentifier(container, id)) // open the board board.onOpening() @@ -3753,8 +3772,7 @@ access(all) contract FGameMishal { // Closes a board access(Host) fun closeBoard(_ container: Address, _ id: UInt64) { - let boardIdentifier = BoardIdentifier(container, id) - let board = boardIdentifier.borrowWritableBoard() + let board = self.borrowInternalWritableBoard(container, id) ?? panic("Board not found") assert(board.isActive() == false, message: "Board should be inactive") @@ -3785,12 +3803,23 @@ access(all) contract FGameMishal { 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) { - return BoardIdentifier(container, id).borrowWritableBoard() + if let container = self.borrowBoardContainer(container) { + return container.borrowWritableBoard(id) + } } } return nil From c1c89d0aa8504c93b7aac748898e82c8e473fcf9 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Sun, 15 Jun 2025 16:10:09 +0800 Subject: [PATCH 44/45] feat: add removeBoardContainer function and corresponding event in FGameMishal contract to improve board container management and enhance gameplay dynamics --- cadence/contracts/FGameMishal.cdc | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cadence/contracts/FGameMishal.cdc b/cadence/contracts/FGameMishal.cdc index 2f3adf6..2b6ccc3 100644 --- a/cadence/contracts/FGameMishal.cdc +++ b/cadence/contracts/FGameMishal.cdc @@ -59,6 +59,7 @@ access(all) contract FGameMishal { 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 ----- @@ -3730,6 +3731,36 @@ access(all) contract FGameMishal { ) } + 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}? { From 444675549dddc3641ccd7639847679743591d6b0 Mon Sep 17 00:00:00 2001 From: Tang Bo Hao Date: Mon, 16 Jun 2025 19:10:17 +0800 Subject: [PATCH 45/45] refactor: remove unused Pawn resource from FGameMishalBattleField contract to streamline code and improve clarity --- cadence/contracts/FGameMishalBattleField.cdc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cadence/contracts/FGameMishalBattleField.cdc b/cadence/contracts/FGameMishalBattleField.cdc index 866b551..6719222 100644 --- a/cadence/contracts/FGameMishalBattleField.cdc +++ b/cadence/contracts/FGameMishalBattleField.cdc @@ -17,11 +17,6 @@ access(all) contract FGameMishalBattleField { access(all) entitlement SessionManage access(all) entitlement Creator - // The Pawn resource is refered to as the character in the game. - access(all) resource Pawn { - - } - // The Commander resource is refered to as the player in the game. access(all) resource Commander { }