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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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 @@ -909,12 +909,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 @@ -108,6 +108,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 @@ -321,6 +325,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: 2 additions & 0 deletions config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
setMercenariesConfig,
setPopulationConfig,
setProductionConfig,
setRealmLevelConfig,
setResourceBuildingConfig,
setSpeedConfig,
setStaminaConfig,
Expand Down Expand Up @@ -56,6 +57,7 @@ console.log("Account set up");
await setBuildingCategoryPopConfig(account, provider);
await setPopulationConfig(account, provider);
await setBuildingConfig(account, provider);
await setRealmLevelConfig(account, provider);
await setResourceBuildingConfig(account, provider);
await setWeightConfig(account, provider);
await setCombatConfig(account, provider);
Expand Down
3 changes: 2 additions & 1 deletion contracts/scripts/system_models.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"ProductionOutput",
"DetachedResource",
"ResourceCost",
"TravelStaminaCostConfig"
"TravelStaminaCostConfig",
"RealmLevelConfig"
],
"DEV_RESOURCE_SYSTEMS": ["Resource", "OwnedResourcesTracker", "Production"],
"DEV_BANK_SYSTEMS": ["Tile", "Position", "Owner", "Bank", "Structure", "StructureCount", "CapacityCategory"],
Expand Down
10 changes: 10 additions & 0 deletions contracts/src/models/config.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,13 @@ pub struct HasClaimedStartingResources {
config_id: ID,
claimed: bool,
}


#[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
11 changes: 6 additions & 5 deletions contracts/src/models/realm.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,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) -> u8 {
Copy link

Choose a reason for hiding this comment

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

Consider making the max_level configurable rather than hardcoded. This would allow for easier adjustments in the future if needed.

3
}

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 @@ -33,26 +33,37 @@ mod building_systems {
building_category: BuildingCategory,
produce_resource_type: Option<u8>,
) {
assert!(directions.len() <= 4, "cannot build on selected tile");
let mut building_coord: Coord = BuildingCustomImpl::center();
loop {
match directions.pop_front() {
Option::Some(direction) => { building_coord = building_coord.neighbor(*direction); },
Option::None => { break; }
}
};

// 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().into() + 1, "building outside of max bound");
assert!(directions_count <= realm.level.into() + 1, "building outside of what realm level allows");
Copy link

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

get!(world, entity_id, EntityOwner).assert_caller_owner(world);
// get the location of the building
let mut building_coord: Coord = BuildingCustomImpl::center();
loop {
match directions.pop_front() {
Option::Some(direction) => { building_coord = building_coord.neighbor(*direction); },
Option::None => { break; }
}
};

// todo: check that entity is a realm
// create the building
let building: Building = 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 @@ -125,9 +125,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 @@ -152,9 +152,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
45 changes: 44 additions & 1 deletion contracts/src/systems/config/contracts.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ trait IRealmFreeMintConfig {
fn set_mint_config(ref world: IWorldDispatcher, config_id: ID, resources: Span<(u8, u128)>);
}

#[dojo::interface]
trait IRealmLevelConfig {
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 @@ -149,7 +154,7 @@ mod config_systems {
CapacityConfig, SpeedConfig, WeightConfig, WorldConfig, LevelingConfig, RealmFreeMintConfig, MapConfig,
TickConfig, ProductionConfig, BankConfig, TroopConfig, BuildingConfig, BuildingCategoryPopConfig,
PopulationConfig, HyperstructureResourceConfig, HyperstructureConfig, StaminaConfig, StaminaRefillConfig,
MercenariesConfig, BattleConfig, TravelStaminaCostConfig
MercenariesConfig, BattleConfig, TravelStaminaCostConfig, RealmLevelConfig
};

use eternum::models::position::{Position, PositionCustomTrait, Coord};
Expand Down Expand Up @@ -570,4 +575,42 @@ mod config_systems {
set!(world, (MercenariesConfig { config_id: WORLD_CONFIG_ID, troops, rewards }));
}
}

#[abi(embed_v0)]
impl RealmLevelConfigCustomImpl of super::IRealmLevelConfig<ContractState> {
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()
})
);
}
}
}
45 changes: 42 additions & 3 deletions contracts/src/systems/realm/contracts.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ trait IRealmSystems {
order: u8,
position: eternum::models::position::Position
) -> 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 @@ -30,17 +31,17 @@ 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};
use eternum::models::config::{CapacityConfigCategory, RealmLevelConfig};
use eternum::models::config::{RealmFreeMintConfig, HasClaimedStartingResources};
use eternum::models::event::{SettleRealmData, EventType};
use eternum::models::map::Tile;
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 @@ -142,6 +143,7 @@ mod realm_systems {
regions,
wonder,
order,
level: 0
},
Position { entity_id: entity_id.into(), x: position.x, y: position.y, },
EntityMetadata { entity_id: entity_id.into(), entity_type: REALM_ENTITY_TYPE, }
Expand Down Expand Up @@ -178,5 +180,42 @@ mod realm_systems {

Copy link

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(), '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