diff --git a/contracts/src/cards/actions.cairo b/contracts/src/cards/actions.cairo index 19e3b2a..dfe86b2 100644 --- a/contracts/src/cards/actions.cairo +++ b/contracts/src/cards/actions.cairo @@ -7,7 +7,7 @@ use spellcrafter::cards::properties::{ requires_hot_gt, requires_light_gt, requires_dark_gt, chaos_delta_fallback, power_delta_fallback, hotcold_delta_fallback, lightdark_delta_fallback }; -use spellcrafter::constants::{CHAOS_STAT, POWER_STAT, HOTCOLD_STAT, LIGHTDARK_STAT, BARRIERS_STAT, POLAR_STAT_MIDPOINT}; +use spellcrafter::constants::{CHAOS_STAT, POWER_STAT, HOTCOLD_STAT, LIGHTDARK_STAT, BARRIERS_STAT, POLAR_STAT_MIDPOINT, TICKS, CHAOS_PER_TICK}; // modify the game state as demanded by this card @@ -86,6 +86,13 @@ fn make_fallback_stat_changes(world: IWorldDispatcher, game_id: u128, card_id: u } } +/// Move time forward for the game by this number of ticks +/// Chaos increases by one point per tick +fn tick(world: IWorldDispatcher, game_id: u128, amount: u32) { + increase_stat(world, game_id, TICKS, amount); + increase_stat(world, game_id, CHAOS_STAT, amount * CHAOS_PER_TICK); +} + fn bust_barrier(world: IWorldDispatcher, game_id: u128) { decrease_stat(world, game_id, BARRIERS_STAT, 1); } diff --git a/contracts/src/components.cairo b/contracts/src/components.cairo index 6895666..0812a54 100644 --- a/contracts/src/components.cairo +++ b/contracts/src/components.cairo @@ -1,7 +1,9 @@ mod owner; mod value_in_game; mod occupied; +mod familiar; use owner::Owner; use value_in_game::ValueInGame; use occupied::Occupied; +use familiar::Familiar; diff --git a/contracts/src/components/familiar.cairo b/contracts/src/components/familiar.cairo new file mode 100644 index 0000000..3983415 --- /dev/null +++ b/contracts/src/components/familiar.cairo @@ -0,0 +1,9 @@ + +// Represents an entity that is a familiar +#[derive(Model, Copy, Drop, Serde)] +struct Familiar { + #[key] + entity_id: u128, + game_id: u128, + familiar_type_id: u128, +} diff --git a/contracts/src/constants.cairo b/contracts/src/constants.cairo index dac8d1f..f5f4c63 100644 --- a/contracts/src/constants.cairo +++ b/contracts/src/constants.cairo @@ -1,12 +1,25 @@ +/// Game Params /// + const INITIAL_BARRIERS: u32 = 3; const CHAOS_PER_FORAGE: u32 = 3; const ITEM_LIMIT: u32 = 7; +const FAMILIAR_LIMIT: u32 = 1; + +// How many ticks each kind of action takes +const TICKS_PER_FORAGE: u32 = 3; +const TICKS_PER_SUMMON: u32 = 5; +const TICKS_PER_SEND: u32 = 3; +const CHAOS_PER_TICK: u32 = 1; + +/// Valid IDs /// // ensure these dont collide with card ids const CHAOS_STAT: u128 = 10000; const POWER_STAT: u128 = 10001; const BARRIERS_STAT: u128 = 10002; const ITEMS_HELD: u128 = 10003; +const FAMILIARS_HELD: u128 = 10004; +const TICKS: u128 = 10005; // this is zero for stats that can go +/- const POLAR_STAT_MIDPOINT: u32 = 2_147_483_647; @@ -14,3 +27,9 @@ const POLAR_STAT_MIDPOINT: u32 = 2_147_483_647; // polar stats const HOTCOLD_STAT: u128 = 10004; const LIGHTDARK_STAT: u128 = 10005; + +// familiars +const RAVENS: u128 = 30001; +const CATS: u128 = 30002; +const SALAMANDERS: u128 = 30003; +const WOLF_SPIDERS: u128 = 30004; diff --git a/contracts/src/systems.cairo b/contracts/src/systems.cairo index c357c5e..4ee4e81 100644 --- a/contracts/src/systems.cairo +++ b/contracts/src/systems.cairo @@ -1,10 +1,11 @@ -use spellcrafter::types::Region; +use spellcrafter::types::{Region, FamiliarType}; #[starknet::interface] trait ISpellCrafter { fn new_game(self: @TContractState) -> u128; fn forage(self: @TContractState, game_id: u128, region: Region) -> u128; fn interact(self: @TContractState, game_id: u128, item_id: u128); + fn summon(self: @TContractState, game_id: u128, familiar_type: FamiliarType) -> u128; } #[dojo::contract] @@ -12,17 +13,22 @@ mod spellcrafter_system { use super::ISpellCrafter; use starknet::get_caller_address; - use spellcrafter::constants::{INITIAL_BARRIERS, BARRIERS_STAT, HOTCOLD_STAT, LIGHTDARK_STAT, POLAR_STAT_MIDPOINT, CHAOS_STAT, ITEMS_HELD, CHAOS_PER_FORAGE, ITEM_LIMIT}; - use spellcrafter::types::Region; - use spellcrafter::components::{Owner, ValueInGame}; + use spellcrafter::constants::{ + INITIAL_BARRIERS, BARRIERS_STAT, HOTCOLD_STAT, LIGHTDARK_STAT, POLAR_STAT_MIDPOINT, + CHAOS_STAT, ITEMS_HELD, CHAOS_PER_FORAGE, ITEM_LIMIT, FAMILIAR_LIMIT, FAMILIARS_HELD, + TICKS_PER_SUMMON, + }; + use spellcrafter::types::{Region, FamiliarType, FamiliarTypeTrait}; + use spellcrafter::components::{Owner, ValueInGame, Familiar}; use spellcrafter::utils::assertions::{assert_caller_is_owner, assert_is_alive}; use spellcrafter::utils::random::pass_check; use spellcrafter::cards::selection::random_card_from_region; - use spellcrafter::cards::actions::{increase_stat, stat_meets_threshold, enact_card, is_dead, bust_barrier}; + use spellcrafter::cards::actions::{ + increase_stat, stat_meets_threshold, enact_card, is_dead, bust_barrier, tick, + }; #[external(v0)] impl SpellCrafterImpl of ISpellCrafter { - fn new_game(self: @ContractState) -> u128 { let world = self.world_dispatcher.read(); let game_id: u128 = world.uuid().into(); @@ -44,8 +50,13 @@ mod spellcrafter_system { let world = self.world_dispatcher.read(); assert_caller_is_owner(world, get_caller_address(), game_id); assert_is_alive(world, game_id); - assert(!stat_meets_threshold(world, game_id, ITEMS_HELD, Option::Some((ITEM_LIMIT, false))), 'Too many items held'); - + assert( + !stat_meets_threshold( + world, game_id, ITEMS_HELD, Option::Some((ITEM_LIMIT, false)) + ), + 'Too many items held' + ); + // TODO This is not simulation safe. Ok for quick protyping only let tx_info = starknet::get_tx_info().unbox(); let seed = tx_info.transaction_hash; @@ -62,6 +73,7 @@ mod spellcrafter_system { return card_id; } + // Place a card owned by the player in this game into the spell fn interact(self: @ContractState, game_id: u128, item_id: u128) { let world = self.world_dispatcher.read(); assert_caller_is_owner(world, get_caller_address(), game_id); @@ -73,7 +85,7 @@ mod spellcrafter_system { let owned = get!(world, (item_id, game_id), ValueInGame).value; assert(owned > 0, 'Item is not owned'); - + let chaos = get!(world, (CHAOS_STAT, game_id), ValueInGame).value; if !pass_check(seed, chaos) { @@ -84,13 +96,43 @@ mod spellcrafter_system { enact_card(world, game_id, item_id); } } + + // summon a familiar which can be sent to retrieve items + fn summon(self: @ContractState, game_id: u128, familiar_type: FamiliarType) -> u128 { + let world = self.world_dispatcher.read(); + assert_caller_is_owner(world, get_caller_address(), game_id); + assert_is_alive(world, game_id); + assert( + !stat_meets_threshold( + world, game_id, FAMILIARS_HELD, Option::Some((FAMILIAR_LIMIT, false)) + ), + 'Too many familiars' + ); + // Move time forward, also increase chaos + tick(world, game_id, TICKS_PER_SUMMON); + + // create a new entity for the familiar + let entity_id: u128 = world.uuid().into(); + set!( + world, + ( + Familiar { entity_id, game_id, familiar_type_id: familiar_type.stat_id() }, + Owner { entity_id, address: get_caller_address() }, + ) + ); + + // increase the total number of familiars held in this game + increase_stat(world, game_id, FAMILIARS_HELD, 1); + + return entity_id; + } } } #[cfg(test)] -mod forage_tests { - use dojo::world::{ IWorldDispatcher, IWorldDispatcherTrait}; +mod forage_tests { + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; use spellcrafter::types::Region; use spellcrafter::utils::testing::{deploy_game, SpellcraftDeployment}; @@ -102,10 +144,7 @@ mod forage_tests { #[test] #[available_gas(300000000000)] fn test_forage() { - let SpellcraftDeployment { - world, - system, - } = deploy_game(); + let SpellcraftDeployment{world, system, } = deploy_game(); let game_id = system.new_game(); let card_id = system.forage(game_id, Region::Forest); @@ -117,13 +156,10 @@ mod forage_tests { #[test] #[available_gas(300000000000)] - #[should_panic(expected: ('Too many items held', 'ENTRYPOINT_FAILED') )] + #[should_panic(expected: ('Too many items held', 'ENTRYPOINT_FAILED'))] fn cannot_exceed_max_items() { - let SpellcraftDeployment { - world, - system - } = deploy_game(); - + let SpellcraftDeployment{world, system } = deploy_game(); + let game_id = system.new_game(); // // pre conditions @@ -155,7 +191,7 @@ mod interact_tests { use array::ArrayTrait; use option::OptionTrait; use serde::Serde; - + use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; use dojo::test_utils::deploy_contract; @@ -165,15 +201,12 @@ mod interact_tests { use super::{spellcrafter_system, ISpellCrafterDispatcher, ISpellCrafterDispatcherTrait}; #[test] - #[should_panic(expected: ('Item is not owned', 'ENTRYPOINT_FAILED') )] + #[should_panic(expected: ('Item is not owned', 'ENTRYPOINT_FAILED'))] #[available_gas(300000000000)] fn reverts_if_card_not_owned() { let CARD_ID: u128 = 1; - let SpellcraftDeployment { - world, - system, - } = deploy_game(); + let SpellcraftDeployment{world, system, } = deploy_game(); let game_id = system.new_game(); system.interact(game_id, CARD_ID); @@ -184,14 +217,11 @@ mod interact_tests { fn works_if_card_owned() { let CARD_ID: u128 = 1; - let SpellcraftDeployment { - world, - system - } = deploy_game(); + let SpellcraftDeployment{world, system } = deploy_game(); let game_id = system.new_game(); - set!(world, ValueInGame{ entity_id: CARD_ID, game_id: game_id, value: 1 }); + set!(world, ValueInGame { entity_id: CARD_ID, game_id: game_id, value: 1 }); system.interact(game_id, CARD_ID); } } diff --git a/contracts/src/types.cairo b/contracts/src/types.cairo index 9836426..6577bea 100644 --- a/contracts/src/types.cairo +++ b/contracts/src/types.cairo @@ -1,3 +1,7 @@ mod region; +mod action; +mod familiar; use region::Region; +use action::{Action, ActionTrait}; +use familiar::{FamiliarType, FamiliarTypeTrait}; diff --git a/contracts/src/types/action.cairo b/contracts/src/types/action.cairo new file mode 100644 index 0000000..e99e0bc --- /dev/null +++ b/contracts/src/types/action.cairo @@ -0,0 +1,22 @@ +/// An action that an entity can be performing while time passes +#[derive(Serde, Copy, Drop, Introspect)] +enum Action { + None: (), + ForageForest: (), + ForageMeadow: (), + ForageVolcano: (), + ForageCave: (), +} + +#[generate_trait] +impl ImplAction of ActionTrait { + fn id(self: Action) -> u8 { + match self { + Action::None => 0, + Action::ForageForest => 1, + Action::ForageMeadow => 2, + Action::ForageVolcano => 3, + Action::ForageCave => 4, + } + } +} diff --git a/contracts/src/types/familiar.cairo b/contracts/src/types/familiar.cairo new file mode 100644 index 0000000..7de3b4b --- /dev/null +++ b/contracts/src/types/familiar.cairo @@ -0,0 +1,31 @@ +use spellcrafter::constants::{RAVENS, CATS, SALAMANDERS, WOLF_SPIDERS}; +use spellcrafter::types::{Action, ActionTrait}; + +#[derive(Serde, Copy, Drop, Introspect)] +enum FamiliarType { + Raven: (), + Cat: (), + Salamanger: (), + WolfSpider: (), +} + +#[generate_trait] +impl ImplFamiliarType of FamiliarTypeTrait { + fn stat_id(self: FamiliarType) -> u128 { + match self { + FamiliarType::Raven => RAVENS, + FamiliarType::Cat => CATS, + FamiliarType::Salamanger => SALAMANDERS, + FamiliarType::WolfSpider => WOLF_SPIDERS, + } + } + + fn default_action_id(self: FamiliarType) -> u8 { + match self { + FamiliarType::Raven => Action::ForageForest.id(), + FamiliarType::Cat => Action::ForageMeadow.id(), + FamiliarType::Salamanger => Action::ForageVolcano.id(), + FamiliarType::WolfSpider => Action::ForageCave.id(), + } + } +} \ No newline at end of file