Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contracts: feature: introduce realm levels #1721

Merged
merged 17 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/src/dojo/contractComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
},
},
Expand Down
5 changes: 5 additions & 0 deletions client/src/dojo/createSystemCalls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down Expand Up @@ -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)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
};
Expand Down
2 changes: 1 addition & 1 deletion contracts/manifests/dev/deployment/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,7 @@
],
"address": "0x76ca3dfc3e96843716f882546f0db96b7da0cf988bdba284b469d0defb2f48f",
"transaction_hash": "0x73f424096734f925628f50730e27889506b6b9141efd4aed165165ef8367ce5",
"block_number": 65,
"block_number": 57,
"seed": "eternum",
"metadata": {
"profile_name": "dev",
Expand Down
4 changes: 2 additions & 2 deletions contracts/manifests/dev/deployment/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -3542,4 +3542,4 @@ key = false
[[models.members]]
name = "realm_l2_contract"
type = "ContractAddress"
key = false
key = false
2 changes: 2 additions & 0 deletions contracts/scripts/system_models.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"DetachedResource",
"ResourceCost",
"TravelStaminaCostConfig",
"RealmLevelConfig",
"RealmMaxLevelConfig",
"TravelFoodCostConfig"
],
"DEV_RESOURCE_SYSTEMS": ["Resource", "OwnedResourcesTracker", "Production"],
Expand Down
18 changes: 18 additions & 0 deletions contracts/src/models/config.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
1 change: 1 addition & 0 deletions contracts/src/models/owner.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ mod tests {
regions: 0,
wonder: 0,
order: 0,
level: 0
}
);

Expand Down
14 changes: 9 additions & 5 deletions contracts/src/models/realm.cairo
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<u8> = unpack_resource_types(self.resource_types_packed, self.resource_types_count);
let mut has_resource = false;
Expand Down
33 changes: 22 additions & 11 deletions contracts/src/systems/buildings/contracts.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,30 @@ mod building_systems {
building_category: BuildingCategory,
produce_resource_type: Option<u8>,
) {
// 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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message 'building outside of what realm level allows' could be more informative. Consider including the current realm level and the required level in the message.

Suggested change
assert!(directions_count <= realm.level.into() + 1, "building outside of what realm level allows");
assert!(directions_count <= realm.level.into() + 1,
&format!("building outside of what realm level allows: current level is {}, required level is {}", realm.level, directions_count - 1));


// 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() {
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions contracts/src/systems/combat/tests/battle_pillage_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand Down
55 changes: 53 additions & 2 deletions contracts/src/systems/config/contracts.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -633,6 +639,51 @@ mod config_systems {
}
}

#[abi(embed_v0)]
impl RealmLevelConfigCustomImpl of super::IRealmLevelConfig<ContractState> {
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<ContractState> {
fn set_settlement_config(
Expand Down
45 changes: 42 additions & 3 deletions contracts/src/systems/realm/contracts.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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};

Expand All @@ -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;
Expand Down Expand Up @@ -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, }
Expand Down Expand Up @@ -191,5 +193,42 @@ mod realm_systems {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider emitting an event when a realm is upgraded. This would be useful for tracking realm progress and could be used by the frontend or other systems.

Suggested change

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));
}
}
}
Loading
Loading