diff --git a/client/src/dojo/contractComponents.ts b/client/src/dojo/contractComponents.ts index 20b56cb72..0995dd061 100644 --- a/client/src/dojo/contractComponents.ts +++ b/client/src/dojo/contractComponents.ts @@ -949,12 +949,13 @@ export function defineContractComponents(world: World) { regions: RecsType.Number, wonder: RecsType.Number, order: RecsType.Number, + level: RecsType.Number, }, { metadata: { namespace: "eternum", name: "Realm", - types: ["u32", "u32", "u128", "u8", "u8", "u8", "u8", "u8", "u8", "u8"], + types: ["u32", "u32", "u128", "u8", "u8", "u8", "u8", "u8", "u8", "u8", "u8"], customTypes: [], }, }, diff --git a/client/src/dojo/createSystemCalls.ts b/client/src/dojo/createSystemCalls.ts index 0695f6b9f..5bee4bdfa 100644 --- a/client/src/dojo/createSystemCalls.ts +++ b/client/src/dojo/createSystemCalls.ts @@ -115,6 +115,10 @@ export function createSystemCalls({ provider }: SetupNetworkResult) { await provider.create_realm(props); }; + const upgrade_realm = async (props: SystemProps.UpgradeRealmProps) => { + await provider.upgrade_realm(props); + }; + const create_multiple_realms = async (props: SystemProps.CreateMultipleRealmsProps) => { await provider.create_multiple_realms(props); }; @@ -336,6 +340,7 @@ export function createSystemCalls({ provider }: SetupNetworkResult) { cancel_order: withQueueing(withErrorHandling(cancel_order)), accept_partial_order: withQueueing(withErrorHandling(accept_partial_order)), create_realm: withQueueing(withErrorHandling(create_realm)), + upgrade_realm: withQueueing(withErrorHandling(upgrade_realm)), create_multiple_realms: withQueueing(withErrorHandling(create_multiple_realms)), transfer_resources: withQueueing(withErrorHandling(transfer_resources)), travel: withQueueing(withErrorHandling(travel)), diff --git a/client/src/dojo/modelManager/__tests__/__BattleManagerMock__.ts b/client/src/dojo/modelManager/__tests__/__BattleManagerMock__.ts index 1ada96054..51d05ca1c 100644 --- a/client/src/dojo/modelManager/__tests__/__BattleManagerMock__.ts +++ b/client/src/dojo/modelManager/__tests__/__BattleManagerMock__.ts @@ -124,6 +124,7 @@ export const generateMockArmyInfo = ( regions: 1, wonder: 1, order: 1, + level: 1, }, homePosition: { entity_id: ARMY_ENTITY_ID, x: 0, y: 0 }, }; diff --git a/contracts/manifests/dev/deployment/manifest.json b/contracts/manifests/dev/deployment/manifest.json index b3613045b..a75cc925d 100644 --- a/contracts/manifests/dev/deployment/manifest.json +++ b/contracts/manifests/dev/deployment/manifest.json @@ -1236,7 +1236,7 @@ ], "address": "0x76ca3dfc3e96843716f882546f0db96b7da0cf988bdba284b469d0defb2f48f", "transaction_hash": "0x73f424096734f925628f50730e27889506b6b9141efd4aed165165ef8367ce5", - "block_number": 65, + "block_number": 57, "seed": "eternum", "metadata": { "profile_name": "dev", diff --git a/contracts/manifests/dev/deployment/manifest.toml b/contracts/manifests/dev/deployment/manifest.toml index e783b1e17..fdad5f2dd 100644 --- a/contracts/manifests/dev/deployment/manifest.toml +++ b/contracts/manifests/dev/deployment/manifest.toml @@ -5,7 +5,7 @@ original_class_hash = "0x6f4515274ee23404789c3351a77107d0ec07508530119822046600c abi = "manifests/dev/deployment/abis/dojo-world.json" address = "0x76ca3dfc3e96843716f882546f0db96b7da0cf988bdba284b469d0defb2f48f" transaction_hash = "0x73f424096734f925628f50730e27889506b6b9141efd4aed165165ef8367ce5" -block_number = 65 +block_number = 57 seed = "eternum" manifest_name = "dojo-world" @@ -3542,4 +3542,4 @@ key = false [[models.members]] name = "realm_l2_contract" type = "ContractAddress" -key = false +key = false \ No newline at end of file diff --git a/contracts/scripts/system_models.json b/contracts/scripts/system_models.json index ccf3419da..2d64e4d6b 100644 --- a/contracts/scripts/system_models.json +++ b/contracts/scripts/system_models.json @@ -28,6 +28,8 @@ "DetachedResource", "ResourceCost", "TravelStaminaCostConfig", + "RealmLevelConfig", + "RealmMaxLevelConfig", "TravelFoodCostConfig" ], "DEV_RESOURCE_SYSTEMS": ["Resource", "OwnedResourcesTracker", "Production"], diff --git a/contracts/src/models/config.cairo b/contracts/src/models/config.cairo index 37ed63353..eced364ba 100644 --- a/contracts/src/models/config.cairo +++ b/contracts/src/models/config.cairo @@ -641,3 +641,21 @@ pub struct HasClaimedStartingResources { config_id: ID, claimed: bool, } + +// speed +#[derive(IntrospectPacked, Copy, Drop, Serde)] +#[dojo::model] +pub struct RealmMaxLevelConfig { + #[key] + config_id: ID, + max_level: u8, +} + +#[derive(IntrospectPacked, Copy, Drop, Serde)] +#[dojo::model] +struct RealmLevelConfig { + #[key] + level: u8, + required_resources_id: ID, + required_resource_count: u8, +} diff --git a/contracts/src/models/owner.cairo b/contracts/src/models/owner.cairo index a5d9bd4f6..f97ea4674 100644 --- a/contracts/src/models/owner.cairo +++ b/contracts/src/models/owner.cairo @@ -71,6 +71,7 @@ mod tests { regions: 0, wonder: 0, order: 0, + level: 0 } ); diff --git a/contracts/src/models/realm.cairo b/contracts/src/models/realm.cairo index 1acbb6f85..bc8f5896b 100644 --- a/contracts/src/models/realm.cairo +++ b/contracts/src/models/realm.cairo @@ -1,5 +1,8 @@ use array::SpanTrait; +use dojo::world::IWorldDispatcher; use eternum::alias::ID; +use eternum::constants::WORLD_CONFIG_ID; +use eternum::models::config::RealmMaxLevelConfig; use eternum::utils::unpack::unpack_resource_types; use starknet::ContractAddress; use traits::Into; @@ -21,15 +24,16 @@ pub struct Realm { regions: u8, wonder: u8, order: u8, + level: u8, } -trait RealmCustomTrait { - fn has_resource(self: Realm, resource_type: u8) -> bool; - fn assert_is_set(self: Realm); -} - +#[generate_trait] impl RealmCustomImpl of RealmCustomTrait { + fn max_level(self: Realm, world: IWorldDispatcher) -> u8 { + get!(world, WORLD_CONFIG_ID, RealmMaxLevelConfig).max_level + } + fn has_resource(self: Realm, resource_type: u8) -> bool { let mut resource_types: Span = unpack_resource_types(self.resource_types_packed, self.resource_types_count); let mut has_resource = false; diff --git a/contracts/src/systems/buildings/contracts.cairo b/contracts/src/systems/buildings/contracts.cairo index 8600d49db..4699f3851 100644 --- a/contracts/src/systems/buildings/contracts.cairo +++ b/contracts/src/systems/buildings/contracts.cairo @@ -34,9 +34,30 @@ mod building_systems { building_category: BuildingCategory, produce_resource_type: Option, ) { + // ensure only realms can make buildings + let realm: Realm = get!(world, entity_id, Realm); + assert!(realm.realm_id != 0, "entity is not a realm"); + + // ensure caller owns the realm + get!(world, entity_id, EntityOwner).assert_caller_owner(world); + + // ensure buildings can't be made outside + // the range of what the realm level allows + let directions_count = directions.len(); + assert!(directions_count > 0, "building cant be made at the center"); + assert!(directions_count <= realm.max_level(world).into() + 1, "building outside of max bound"); + assert!(directions_count <= realm.level.into() + 1, "building outside of what realm level allows"); + + // ensure that the realm produces the resource + if produce_resource_type.is_some() { + let resource_type: u8 = produce_resource_type.unwrap(); + let realm_produces_resource = realm.has_resource(resource_type); + assert!(realm_produces_resource, "realm does not produce specified resource"); + } + + // check if season is over SeasonCustomImpl::assert_season_is_not_over(world); - assert!(directions.len() <= 4, "cannot build on selected tile"); let mut building_coord: Coord = BuildingCustomImpl::center(); loop { match directions.pop_front() { @@ -45,16 +66,6 @@ mod building_systems { } }; - let realm: Realm = get!(world, entity_id, Realm); - assert!(realm.realm_id != 0, "entity is not a realm"); - if produce_resource_type.is_some() { - let resource_type: u8 = produce_resource_type.unwrap(); - let realm_produces_resource = realm.has_resource(resource_type); - assert!(realm_produces_resource, "realm does not produce specified resource"); - } - - get!(world, entity_id, EntityOwner).assert_caller_owner(world); - // todo: check that entity is a realm let (building, building_quantity) = BuildingCustomImpl::create( world, entity_id, building_category, produce_resource_type, building_coord diff --git a/contracts/src/systems/combat/tests/battle_pillage_test.cairo b/contracts/src/systems/combat/tests/battle_pillage_test.cairo index 2cfe98ebe..196ea6ad3 100644 --- a/contracts/src/systems/combat/tests/battle_pillage_test.cairo +++ b/contracts/src/systems/combat/tests/battle_pillage_test.cairo @@ -129,9 +129,9 @@ fn test_battle_pillage__near_max_capacity() { starknet::testing::set_block_timestamp(DEFAULT_BLOCK_TIMESTAMP * 2); - let army_quantity = get!(world, attacker_realm_army_unit_id, Quantity); + let _army_quantity = get!(world, attacker_realm_army_unit_id, Quantity); - let capacity_config = get!(world, 3, CapacityConfig); + let _capacity_config = get!(world, 3, CapacityConfig); combat_system_dispatcher.battle_pillage(attacker_realm_army_unit_id, defender_realm_entity_id); @@ -156,9 +156,9 @@ fn test_simple_battle_pillage() { starknet::testing::set_block_timestamp(DEFAULT_BLOCK_TIMESTAMP * 2); - let army_quantity = get!(world, attacker_realm_army_unit_id, Quantity); + let _army_quantity = get!(world, attacker_realm_army_unit_id, Quantity); - let capacity_config = get!(world, 3, CapacityConfig); + let _capacity_config = get!(world, 3, CapacityConfig); combat_system_dispatcher.battle_pillage(attacker_realm_army_unit_id, defender_realm_entity_id); diff --git a/contracts/src/systems/config/contracts.cairo b/contracts/src/systems/config/contracts.cairo index 2a6cf09aa..809230193 100644 --- a/contracts/src/systems/config/contracts.cairo +++ b/contracts/src/systems/config/contracts.cairo @@ -21,6 +21,12 @@ trait IRealmFreeMintConfig { fn set_mint_config(ref world: IWorldDispatcher, config_id: ID, resources: Span<(u8, u128)>); } +#[dojo::interface] +trait IRealmLevelConfig { + fn set_realm_max_level_config(ref world: IWorldDispatcher, new_max_level: u8); + fn set_realm_level_config(ref world: IWorldDispatcher, level: u8, resources: Span<(u8, u128)>); +} + #[dojo::interface] trait IWeightConfig { fn set_weight_config(ref world: IWorldDispatcher, entity_type: ID, weight_gram: u128); @@ -178,8 +184,8 @@ mod config_systems { CapacityConfig, SpeedConfig, WeightConfig, WorldConfig, LevelingConfig, RealmFreeMintConfig, MapConfig, TickConfig, ProductionConfig, BankConfig, TroopConfig, BuildingConfig, BuildingCategoryPopConfig, PopulationConfig, HyperstructureResourceConfig, HyperstructureConfig, StaminaConfig, StaminaRefillConfig, - MercenariesConfig, BattleConfig, TravelStaminaCostConfig, SettlementConfig, BuildingGeneralConfig, - TravelFoodCostConfig + MercenariesConfig, BattleConfig, TravelStaminaCostConfig, RealmLevelConfig, BuildingGeneralConfig, + RealmMaxLevelConfig, SettlementConfig, TravelFoodCostConfig }; use eternum::models::hyperstructure::SeasonCustomImpl; @@ -633,6 +639,51 @@ mod config_systems { } } + #[abi(embed_v0)] + impl RealmLevelConfigCustomImpl of super::IRealmLevelConfig { + fn set_realm_max_level_config(ref world: IWorldDispatcher, new_max_level: u8) { + // ensure only admin can set this + assert_caller_is_admin(world); + + set!(world, (RealmMaxLevelConfig { config_id: WORLD_CONFIG_ID, max_level: new_max_level })); + } + + fn set_realm_level_config(ref world: IWorldDispatcher, level: u8, mut resources: Span<(u8, u128)>) { + // ensure only admin can set this + assert_caller_is_admin(world); + + let detached_resource_id = world.uuid(); + let detached_resource_count = resources.len(); + let mut index = 0; + for ( + resource_type, resource_amount + ) in resources { + let (resource_type, resource_amount) = (*resource_type, *resource_amount); + assert(resource_amount > 0, 'amount must not be 0'); + + set!( + world, + ( + DetachedResource { + entity_id: detached_resource_id, index, resource_type, resource_amount: resource_amount + }, + ) + ); + + index += 1; + }; + + set!( + world, + (RealmLevelConfig { + level, + required_resources_id: detached_resource_id.into(), + required_resource_count: detached_resource_count.try_into().unwrap() + }) + ); + } + } + #[abi(embed_v0)] impl ISettlementConfig of super::ISettlementConfig { fn set_settlement_config( diff --git a/contracts/src/systems/realm/contracts.cairo b/contracts/src/systems/realm/contracts.cairo index ebbec3337..9fe008e3b 100644 --- a/contracts/src/systems/realm/contracts.cairo +++ b/contracts/src/systems/realm/contracts.cairo @@ -15,6 +15,7 @@ trait IRealmSystems { wonder: u8, order: u8, ) -> ID; + fn upgrade_level(ref world: IWorldDispatcher, realm_id: ID); fn mint_starting_resources(ref world: IWorldDispatcher, config_id: ID, entity_id: ID) -> ID; } @@ -29,7 +30,7 @@ mod realm_systems { use eternum::constants::REALM_ENTITY_TYPE; use eternum::constants::{WORLD_CONFIG_ID, REALM_FREE_MINT_CONFIG_ID, MAX_REALMS_PER_ADDRESS}; use eternum::models::capacity::{CapacityCategory}; - use eternum::models::config::{CapacityConfigCategory, SettlementConfig, SettlementConfigImpl}; + use eternum::models::config::{CapacityConfigCategory, RealmLevelConfig, SettlementConfig, SettlementConfigImpl}; use eternum::models::config::{RealmFreeMintConfig, HasClaimedStartingResources}; use eternum::models::event::{SettleRealmData, EventType}; @@ -38,10 +39,10 @@ mod realm_systems { use eternum::models::metadata::EntityMetadata; use eternum::models::movable::Movable; use eternum::models::name::{AddressName}; - use eternum::models::owner::{Owner, EntityOwner}; + use eternum::models::owner::{Owner, EntityOwner, EntityOwnerCustomTrait}; use eternum::models::position::{Position, Coord}; use eternum::models::quantity::QuantityTracker; - use eternum::models::realm::{Realm, RealmCustomTrait}; + use eternum::models::realm::{Realm, RealmCustomTrait, RealmCustomImpl}; use eternum::models::resources::{DetachedResource, Resource, ResourceCustomImpl, ResourceCustomTrait}; use eternum::models::structure::{Structure, StructureCategory, StructureCount, StructureCountCustomTrait}; use eternum::systems::map::contracts::map_systems::InternalMapSystemsImpl; @@ -153,6 +154,7 @@ mod realm_systems { regions, wonder, order, + level: 0 }, Position { entity_id: entity_id.into(), x: coord.x, y: coord.y, }, EntityMetadata { entity_id: entity_id.into(), entity_type: REALM_ENTITY_TYPE, } @@ -191,5 +193,42 @@ mod realm_systems { entity_id.into() } + + + fn upgrade_level(ref world: IWorldDispatcher, realm_id: ID) { + // ensure caller owns the realm + get!(world, realm_id, EntityOwner).assert_caller_owner(world); + + // ensure entity is a realm + let mut realm = get!(world, realm_id, Realm); + realm.assert_is_set(); + + // ensure realm is not already at max level + assert(realm.level < realm.max_level(world), 'realm is already at max level'); + + // make payment to upgrade to next level + let next_level = realm.level + 1; + let realm_level_config = get!(world, next_level, RealmLevelConfig); + let required_resources_id = realm_level_config.required_resources_id; + let required_resource_count = realm_level_config.required_resource_count; + let mut index = 0; + loop { + if index == required_resource_count { + break; + } + + let mut required_resource = get!(world, (required_resources_id, index), DetachedResource); + + // burn resource from realm + let mut realm_resource = ResourceCustomImpl::get(world, (realm_id, required_resource.resource_type)); + realm_resource.burn(required_resource.resource_amount); + realm_resource.save(world); + index += 1; + }; + + // set new level + realm.level = next_level; + set!(world, (realm)); + } } } diff --git a/contracts/src/systems/realm/tests.cairo b/contracts/src/systems/realm/tests.cairo index b38f346fe..f99fe952e 100644 --- a/contracts/src/systems/realm/tests.cairo +++ b/contracts/src/systems/realm/tests.cairo @@ -7,7 +7,8 @@ use eternum::models::map::Tile; use eternum::models::owner::Owner; use eternum::models::position::{Position, Coord}; -use eternum::models::realm::Realm; +use eternum::models::realm::{Realm, RealmCustomTrait}; +use eternum::models::resources::DetachedResource; use eternum::models::resources::Resource; use eternum::systems::config::contracts::{ @@ -23,7 +24,7 @@ use eternum::utils::testing::{ spawn_realm, get_default_realm_pos, generate_realm_positions, spawn_hyperstructure, get_default_hyperstructure_coord }, - config::{set_combat_config, set_capacity_config, set_settlement_config} + config::{set_combat_config, set_capacity_config, set_realm_level_config, set_settlement_config} }; use starknet::contract_address_const; @@ -45,6 +46,7 @@ fn setup() -> (IWorldDispatcher, IRealmSystemsDispatcher) { let config_systems_address = deploy_system(world, config_systems::TEST_CLASS_HASH); set_capacity_config(config_systems_address); + set_realm_level_config(config_systems_address); set_settlement_config(config_systems_address); // set initially minted resources @@ -165,3 +167,150 @@ fn test_mint_starting_resources_as_not_realm() { realm_systems_dispatcher.mint_starting_resources(REALM_FREE_MINT_CONFIG_ID, hyperstructure_entity_id); } + +#[test] +#[available_gas(3000000000000)] +fn test_upgrade_level_success() { + let (world, realm_systems_dispatcher) = setup(); + + // Spawn a realm + let realm_entity_id = spawn_realm(world, realm_systems_dispatcher, get_default_realm_pos()); + + // Add required resources for upgrade + let required_resources = array![(ResourceTypes::WHEAT, 100), (ResourceTypes::WOOD, 100),]; + for (resource_type, amount) in required_resources + .span() { + let mut resource = get!(world, (realm_entity_id, *resource_type), Resource); + resource.balance += *amount; + set!(world, (resource)); + }; + + // Upgrade level + realm_systems_dispatcher.upgrade_level(realm_entity_id); + + // Check if level increased + let realm = get!(world, realm_entity_id, Realm); + assert(realm.level == 1, 'Realm level should be 1'); + + // Check if resources were consumed + for (resource_type, _amount) in required_resources + .span() { + let resource = get!(world, (realm_entity_id, *resource_type), Resource); + assert(resource.balance == 0, 'Resource should be consumed'); + } +} + +#[test] +#[available_gas(3000000000000)] +#[should_panic(expected: ('Not Owner', 'ENTRYPOINT_FAILED'))] +fn test_upgrade_level_not_owner() { + let (world, realm_systems_dispatcher) = setup(); + + // Spawn a realm + let realm_entity_id = spawn_realm(world, realm_systems_dispatcher, get_default_realm_pos()); + + // Set a different caller + starknet::testing::set_contract_address(contract_address_const::<'not_owner'>()); + + // Attempt to upgrade level + realm_systems_dispatcher.upgrade_level(realm_entity_id); +} + +#[test] +#[available_gas(3000000000000)] +#[should_panic(expected: ('Entity is not a realm', 'ENTRYPOINT_FAILED'))] +fn test_upgrade_level_not_realm() { + let (_world, realm_systems_dispatcher) = setup(); + + // Use a non-existent entity ID + let non_realm_entity_id = 12345; + + // Attempt to upgrade level + realm_systems_dispatcher.upgrade_level(non_realm_entity_id); +} + +#[test] +#[available_gas(3000000000000)] +#[should_panic(expected: ('realm is already at max level', 'ENTRYPOINT_FAILED'))] +fn test_upgrade_level_max_level() { + let (world, realm_systems_dispatcher) = setup(); + + // Spawn a realm + let realm_entity_id = spawn_realm(world, realm_systems_dispatcher, get_default_realm_pos()); + + // Set realm to max level + let mut realm = get!(world, realm_entity_id, Realm); + realm.level = realm.max_level(world); + set!(world, (realm)); + + // Attempt to upgrade level + realm_systems_dispatcher.upgrade_level(realm_entity_id); +} + +#[test] +#[available_gas(3000000000000)] +#[should_panic( + expected: ( + "not enough resources, Resource (entity id: 5, resource type: WHEAT, balance: 0). deduction: 100", + 'ENTRYPOINT_FAILED' + ) +)] +fn test_upgrade_level_insufficient_resources() { + let (world, realm_systems_dispatcher) = setup(); + + // Spawn a realm + let realm_entity_id = spawn_realm(world, realm_systems_dispatcher, get_default_realm_pos()); + + // Attempt to upgrade level without adding resources + realm_systems_dispatcher.upgrade_level(realm_entity_id); +} + +#[test] +#[available_gas(3000000000000)] +fn test_upgrade_level_multiple_times() { + let (world, realm_systems_dispatcher) = setup(); + + // Spawn a realm + let realm_entity_id = spawn_realm(world, realm_systems_dispatcher, get_default_realm_pos()); + + // Add more than enough resources for multiple upgrades + let required_resources = array![ + (ResourceTypes::WHEAT, 1000), + (ResourceTypes::WOOD, 1000), + (ResourceTypes::STONE, 1000), + (ResourceTypes::FISH, 1000), + (ResourceTypes::COAL, 1000), + (ResourceTypes::IRONWOOD, 1000), + ]; + for (resource_type, amount) in required_resources + .span() { + let mut resource = get!(world, (realm_entity_id, *resource_type), Resource); + resource.balance += *amount; + set!(world, (resource)); + }; + + // Upgrade level multiple times + realm_systems_dispatcher.upgrade_level(realm_entity_id); + realm_systems_dispatcher.upgrade_level(realm_entity_id); + realm_systems_dispatcher.upgrade_level(realm_entity_id); + + // Check if level increased correctly + let realm = get!(world, realm_entity_id, Realm); + assert(realm.level == 3, 'Realm level should be 3'); + + // Check if resources were consumed correctly + let resource_types = array![ + ResourceTypes::WHEAT, + ResourceTypes::WOOD, + ResourceTypes::STONE, + ResourceTypes::FISH, + ResourceTypes::COAL, + ResourceTypes::IRONWOOD + ]; + for resource_type in resource_types + .span() { + let resource = get!(world, (realm_entity_id, *resource_type), Resource); + assert!(resource.balance < 1000, "Resource should be partially consumed"); + assert!(resource.balance > 0, "Resource should not be fully consumed"); + } +} diff --git a/contracts/src/systems/transport/tests/travel_systems_tests.cairo b/contracts/src/systems/transport/tests/travel_systems_tests.cairo index 94a1c3f39..44dd5f7ff 100644 --- a/contracts/src/systems/transport/tests/travel_systems_tests.cairo +++ b/contracts/src/systems/transport/tests/travel_systems_tests.cairo @@ -134,6 +134,7 @@ fn test_travel_with_realm_bonus() { regions: 0, wonder: 0, order: 0, + level: 0 }, Level { entity_id: realm_entity_id, level: LevelIndex::TRAVEL.into() + 4, valid_until: 10000000, }, LevelingConfig { @@ -208,6 +209,7 @@ fn test_travel_with_realm_and_order_bonus() { regions: 0, wonder: 0, order: realm_order_id.into(), + level: 0 }, Level { entity_id: realm_entity_id, level: LevelIndex::TRAVEL.into() + 4, valid_until: 10000000, }, LevelingConfig { diff --git a/contracts/src/utils/testing/config.cairo b/contracts/src/utils/testing/config.cairo index 8a9df31d7..ed055bb9f 100644 --- a/contracts/src/utils/testing/config.cairo +++ b/contracts/src/utils/testing/config.cairo @@ -19,7 +19,8 @@ use eternum::systems::config::contracts::{ ITickConfigDispatcherTrait, IMapConfigDispatcher, IMapConfigDispatcherTrait, IWeightConfigDispatcher, IWeightConfigDispatcherTrait, IProductionConfigDispatcher, IProductionConfigDispatcherTrait, ITravelStaminaCostConfigDispatcher, ITravelStaminaCostConfigDispatcherTrait, IBattleConfigDispatcher, - IBattleConfigDispatcherTrait, ITravelFoodCostConfigDispatcher, ITravelFoodCostConfigDispatcherTrait + IBattleConfigDispatcherTrait, ITravelFoodCostConfigDispatcher, ITravelFoodCostConfigDispatcherTrait, + IRealmLevelConfigDispatcher, IRealmLevelConfigDispatcherTrait }; use eternum::utils::testing::constants::{ @@ -210,3 +211,14 @@ fn set_weight_config(config_systems_address: ContractAddress) { } } +fn set_realm_level_config(config_systems_address: ContractAddress) { + IRealmLevelConfigDispatcher { contract_address: config_systems_address } + .set_realm_level_config(1, array![(ResourceTypes::WHEAT, 100), (ResourceTypes::WOOD, 100),].span()); + + IRealmLevelConfigDispatcher { contract_address: config_systems_address } + .set_realm_level_config(2, array![(ResourceTypes::STONE, 200), (ResourceTypes::FISH, 200),].span()); + + IRealmLevelConfigDispatcher { contract_address: config_systems_address } + .set_realm_level_config(3, array![(ResourceTypes::COAL, 300), (ResourceTypes::IRONWOOD, 300),].span()); +} + diff --git a/contracts/src/utils/testing/world.cairo b/contracts/src/utils/testing/world.cairo index 71987e064..2939b2149 100644 --- a/contracts/src/utils/testing/world.cairo +++ b/contracts/src/utils/testing/world.cairo @@ -17,7 +17,7 @@ use eternum::models::config::{ stamina_refill_config, tick_config, map_config, realm_free_mint_config, mercenaries_config, leveling_config, production_config, bank_config, building_config, troop_config, battle_config, building_category_pop_config, population_config, has_claimed_starting_resources, hyperstructure_config, travel_stamina_cost_config, - travel_food_cost_config, settlement_config + realm_level_config, realm_max_level_config, travel_food_cost_config, settlement_config }; use eternum::models::guild::{guild, guild_member, guild_whitelist}; use eternum::models::hyperstructure::{ @@ -129,6 +129,8 @@ fn spawn_eternum() -> IWorldDispatcher { capacity_category::TEST_CLASS_HASH, production_deadline::TEST_CLASS_HASH, travel_stamina_cost_config::TEST_CLASS_HASH, + realm_level_config::TEST_CLASS_HASH, + realm_max_level_config::TEST_CLASS_HASH, travel_food_cost_config::TEST_CLASS_HASH, ]; diff --git a/sdk/packages/eternum/src/config/index.ts b/sdk/packages/eternum/src/config/index.ts index 094f28b54..d7ec97289 100644 --- a/sdk/packages/eternum/src/config/index.ts +++ b/sdk/packages/eternum/src/config/index.ts @@ -12,6 +12,8 @@ import { HYPERSTRUCTURE_CREATION_COSTS, HYPERSTRUCTURE_TIME_BETWEEN_SHARES_CHANGE_S, HYPERSTRUCTURE_TOTAL_COSTS, + REALM_MAX_LEVEL, + REALM_UPGRADE_COSTS, RESOURCE_BUILDING_COSTS, RESOURCE_INPUTS, RESOURCE_OUTPUTS, @@ -56,6 +58,8 @@ export class EternumConfig { await setWeightConfig(config); await setBattleConfig(config); await setCombatConfig(config); + await setRealmUpgradeConfig(config); + await setRealmMaxLevelConfig(config); await setupGlobals(config); await setCapacityConfig(config); await setSpeedConfig(config); @@ -194,6 +198,42 @@ export const setBuildingConfig = async (config: Config) => { console.log(`Configuring building cost config ${tx.statusReceipt}...`); }; +export const setRealmUpgradeConfig = async (config: Config) => { + const calldataArray = []; + const REALM_UPGRADE_COSTS_SCALED = scaleResourceInputs( + REALM_UPGRADE_COSTS, + config.config.resources.resourceMultiplier, + ); + for (const level of Object.keys(REALM_UPGRADE_COSTS_SCALED) as unknown as number[]) { + if (REALM_UPGRADE_COSTS_SCALED[level].length !== 0) { + const calldata = { + level, + cost_of_level: REALM_UPGRADE_COSTS_SCALED[level].map((cost) => { + return { + ...cost, + amount: cost.amount * config.config.resources.resourcePrecision, + }; + }), + }; + + calldataArray.push(calldata); + } + } + + const tx = await config.provider.set_realm_level_config({ signer: config.account, calls: calldataArray }); + console.log(`Configuring realm level cost config ${tx.statusReceipt}...`); +}; + +export const setRealmMaxLevelConfig = async (config: Config) => { + const new_max_level = REALM_MAX_LEVEL - 1; + + const tx = await config.provider.set_realm_max_level_config({ + signer: config.account, + new_max_level, + }); + console.log(`Configuring realm max level config ${tx.statusReceipt}...`); +}; + export const setResourceBuildingConfig = async (config: Config) => { const calldataArray = []; diff --git a/sdk/packages/eternum/src/constants/realmLevels.ts b/sdk/packages/eternum/src/constants/realmLevels.ts index 2db65e017..800c430be 100644 --- a/sdk/packages/eternum/src/constants/realmLevels.ts +++ b/sdk/packages/eternum/src/constants/realmLevels.ts @@ -14,6 +14,8 @@ export enum RealmLevelNames { Empire = "Empire", } +export const REALM_MAX_LEVEL = Object.keys(RealmLevels).length / 2; + export const REALM_UPGRADE_COSTS = { [RealmLevels.Settlement]: [], diff --git a/sdk/packages/eternum/src/provider/index.ts b/sdk/packages/eternum/src/provider/index.ts index 5a32f1432..3ed929604 100644 --- a/sdk/packages/eternum/src/provider/index.ts +++ b/sdk/packages/eternum/src/provider/index.ts @@ -207,6 +207,16 @@ export class EternumProvider extends EnhancedDojoProvider { return await this.waitForTransactionWithCheck(tx.transaction_hash); } + public async upgrade_realm(props: SystemProps.UpgradeRealmProps) { + const { realm_entity_id, signer } = props; + + return await this.executeAndCheckTransaction(signer, { + contractAddress: getContractByName(this.manifest, `${NAMESPACE}-realm_systems`), + entrypoint: "upgrade_level", + calldata: [realm_entity_id], + }); + } + create_multiple_realms = async (props: SystemProps.CreateMultipleRealmsProps) => { let { realms, signer } = props; @@ -901,6 +911,35 @@ export class EternumProvider extends EnhancedDojoProvider { }); } + public async set_realm_level_config(props: SystemProps.setRealmUpgradeConfigProps) { + const { calls, signer } = props; + + return await this.executeAndCheckTransaction( + signer, + calls.map((call) => { + return { + contractAddress: getContractByName(this.manifest, `${NAMESPACE}-config_systems`), + entrypoint: "set_realm_level_config", + calldata: [ + call.level, + call.cost_of_level.length, + ...call.cost_of_level.flatMap(({ resource, amount }) => [resource, amount]), + ], + }; + }), + ); + } + + public async set_realm_max_level_config(props: SystemProps.SetRealmMaxLevelConfigProps) { + const { new_max_level, signer } = props; + + return await this.executeAndCheckTransaction(signer, { + contractAddress: getContractByName(this.manifest, `${NAMESPACE}-config_systems`), + entrypoint: "set_realm_max_level_config", + calldata: [new_max_level], + }); + } + public async set_building_config(props: SystemProps.SetBuildingConfigProps) { const { calls, signer } = props; diff --git a/sdk/packages/eternum/src/types/provider.ts b/sdk/packages/eternum/src/types/provider.ts index dbec57c2d..7af56cd22 100644 --- a/sdk/packages/eternum/src/types/provider.ts +++ b/sdk/packages/eternum/src/types/provider.ts @@ -157,6 +157,9 @@ export interface CreateMultipleRealmsProps extends SystemSigner { } export interface CreateRealmProps extends Realm, SystemSigner {} +export interface UpgradeRealmProps extends SystemSigner { + realm_entity_id: num.BigNumberish; +} export interface TransferItemsProps extends SystemSigner { sender_id: num.BigNumberish; @@ -462,6 +465,17 @@ export interface SetBuildingConfigProps extends SystemSigner { }[]; } +export interface setRealmUpgradeConfigProps extends SystemSigner { + calls: { + level: num.BigNumberish; + cost_of_level: ResourceCosts[]; + }[]; +} + +export interface SetRealmMaxLevelConfigProps extends SystemSigner { + new_max_level: num.BigNumberish; +} + export interface SetWorldConfigProps extends SystemSigner { admin_address: num.BigNumberish; realm_l2_contract: num.BigNumberish; diff --git a/sdk/packages/eternum/src/utils/index.ts b/sdk/packages/eternum/src/utils/index.ts index a8d0c929a..06aea45c7 100644 --- a/sdk/packages/eternum/src/utils/index.ts +++ b/sdk/packages/eternum/src/utils/index.ts @@ -5,6 +5,7 @@ import { HYPERSTRUCTURE_CREATION_COSTS, HYPERSTRUCTURE_TOTAL_COSTS, QUEST_RESOURCES, + REALM_UPGRADE_COSTS, RESOURCE_BUILDING_COSTS, RESOURCE_INPUTS, RESOURCE_OUTPUTS, @@ -111,6 +112,10 @@ export const STRUCTURE_COSTS_SCALED: ResourceInputs = scaleResourceInputs( EternumGlobalConfig.resources.resourceMultiplier, ); +export const REALM_UPGRADE_COSTS_SCALED: ResourceInputs = scaleResourceInputs( + REALM_UPGRADE_COSTS, + EternumGlobalConfig.resources.resourceMultiplier, +); export const HYPERSTRUCTURE_CONSTRUCTION_COSTS_SCALED: { resource: number; amount: number }[] = scaleResources( HYPERSTRUCTURE_CONSTRUCTION_COSTS, EternumGlobalConfig.resources.resourceMultiplier,