Repository: mchekin/rpg Branch: master Commit: 5e4b129f4c9a Files: 371 Total size: 532.7 KB Directory structure: gitextract_b2fve_hr/ ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── app/ │ ├── Console/ │ │ └── Kernel.php │ ├── Exceptions/ │ │ └── Handler.php │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── Api/ │ │ │ │ ├── ManageInventoryController.php │ │ │ │ ├── ManageStoreController.php │ │ │ │ └── TradeController.php │ │ │ ├── Auth/ │ │ │ │ ├── ConfirmPasswordController.php │ │ │ │ ├── ForgotPasswordController.php │ │ │ │ ├── LoginController.php │ │ │ │ ├── RegisterController.php │ │ │ │ ├── ResetPasswordController.php │ │ │ │ └── VerificationController.php │ │ │ ├── BattleController.php │ │ │ ├── CharacterBattleController.php │ │ │ ├── CharacterController.php │ │ │ ├── CharacterMessageController.php │ │ │ ├── CharacterStoreController.php │ │ │ ├── Controller.php │ │ │ ├── InventoryController.php │ │ │ ├── ItemCreateController.php │ │ │ ├── LocationController.php │ │ │ ├── MessageController.php │ │ │ ├── OwnStoreController.php │ │ │ └── ProfilePictureController.php │ │ ├── Kernel.php │ │ ├── Middleware/ │ │ │ ├── Authenticate.php │ │ │ ├── CanAttack.php │ │ │ ├── CanMoveToLocation.php │ │ │ ├── EncryptCookies.php │ │ │ ├── HasCharacter.php │ │ │ ├── IsAdmin.php │ │ │ ├── IsCharacterLocation.php │ │ │ ├── NoCharacterYet.php │ │ │ ├── PreventRequestsDuringMaintenance.php │ │ │ ├── RedirectIfAuthenticated.php │ │ │ ├── TrimStrings.php │ │ │ ├── TrustHosts.php │ │ │ ├── TrustProxies.php │ │ │ ├── UpdateLastUserActivity.php │ │ │ ├── UserOwnsCharacter.php │ │ │ └── VerifyCsrfToken.php │ │ ├── Requests/ │ │ │ ├── CreateCharacterRequest.php │ │ │ ├── CreateItemRequest.php │ │ │ ├── UpdateCharacterAttributeRequest.php │ │ │ └── UploadImageRequest.php │ │ └── ViewComposers/ │ │ ├── BattlesComposer.php │ │ ├── CharacterGeneralInfoComposer.php │ │ ├── CharacterMessagesComposer.php │ │ └── MessagesComposer.php │ ├── Models/ │ │ ├── Battle.php │ │ ├── BattleRound.php │ │ ├── BattleTurn.php │ │ ├── Character.php │ │ ├── Image.php │ │ ├── Inventory.php │ │ ├── Item.php │ │ ├── ItemPrototype.php │ │ ├── Location.php │ │ ├── Message.php │ │ ├── Race.php │ │ ├── Store.php │ │ └── User.php │ ├── Modules/ │ │ ├── Battle/ │ │ │ ├── Application/ │ │ │ │ └── Contracts/ │ │ │ │ └── BattleRepositoryInterface.php │ │ │ ├── Domain/ │ │ │ │ ├── Battle.php │ │ │ │ ├── BattleId.php │ │ │ │ ├── BattleRound.php │ │ │ │ ├── BattleRounds.php │ │ │ │ ├── BattleTurn.php │ │ │ │ ├── BattleTurnResult.php │ │ │ │ └── BattleTurns.php │ │ │ └── Infrastructure/ │ │ │ └── Repositories/ │ │ │ └── BattleRepository.php │ │ ├── Character/ │ │ │ ├── Application/ │ │ │ │ ├── Commands/ │ │ │ │ │ ├── AttackCharacterCommand.php │ │ │ │ │ ├── CreateCharacterCommand.php │ │ │ │ │ ├── IncreaseAttributeCommand.php │ │ │ │ │ └── MoveCharacterCommand.php │ │ │ │ ├── Contracts/ │ │ │ │ │ ├── CharacterRepositoryInterface.php │ │ │ │ │ ├── LocationRepositoryInterface.php │ │ │ │ │ └── RaceRepositoryInterface.php │ │ │ │ ├── Factories/ │ │ │ │ │ └── CharacterFactory.php │ │ │ │ └── Services/ │ │ │ │ └── CharacterService.php │ │ │ ├── Domain/ │ │ │ │ ├── Attributes.php │ │ │ │ ├── Character.php │ │ │ │ ├── CharacterId.php │ │ │ │ ├── CharacterType.php │ │ │ │ ├── Gender.php │ │ │ │ ├── HitPoints.php │ │ │ │ ├── LocationId.php │ │ │ │ ├── Name.php │ │ │ │ ├── Race.php │ │ │ │ ├── Reputation.php │ │ │ │ └── Statistics.php │ │ │ ├── Infrastructure/ │ │ │ │ ├── ReconstitutionFactories/ │ │ │ │ │ └── CharacterReconstitutionFactory.php │ │ │ │ └── Repositories/ │ │ │ │ ├── CharacterRepository.php │ │ │ │ ├── LocationRepository.php │ │ │ │ └── RaceRepository.php │ │ │ └── UI/ │ │ │ └── Http/ │ │ │ └── CommandMappers/ │ │ │ ├── AttackCharacterCommandMapper.php │ │ │ ├── CreateCharacterCommandMapper.php │ │ │ ├── IncreaseAttributeCommandMapper.php │ │ │ └── MoveCharacterCommandMapper.php │ │ ├── Equipment/ │ │ │ ├── Application/ │ │ │ │ ├── Commands/ │ │ │ │ │ ├── AddItemToInventoryCommand.php │ │ │ │ │ ├── CreateInventoryCommand.php │ │ │ │ │ ├── CreateItemCommand.php │ │ │ │ │ └── EquipItemCommand.php │ │ │ │ ├── Contracts/ │ │ │ │ │ ├── InventoryRepositoryInterface.php │ │ │ │ │ ├── ItemPrototypeRepositoryInterface.php │ │ │ │ │ └── ItemRepositoryInterface.php │ │ │ │ ├── Factories/ │ │ │ │ │ └── ItemFactory.php │ │ │ │ └── Services/ │ │ │ │ ├── InventoryService.php │ │ │ │ └── ItemService.php │ │ │ ├── Domain/ │ │ │ │ ├── Inventory.php │ │ │ │ ├── InventoryId.php │ │ │ │ ├── InventoryItem.php │ │ │ │ ├── InventorySlot.php │ │ │ │ ├── Item.php │ │ │ │ ├── ItemEffect.php │ │ │ │ ├── ItemId.php │ │ │ │ ├── ItemPrice.php │ │ │ │ ├── ItemPrototype.php │ │ │ │ ├── ItemPrototypeId.php │ │ │ │ ├── ItemStatus.php │ │ │ │ ├── ItemType.php │ │ │ │ └── Money.php │ │ │ ├── Infrastructure/ │ │ │ │ ├── ReconstitutionFactories/ │ │ │ │ │ ├── InventoryItemReconstitutionFactory.php │ │ │ │ │ ├── InventoryReconstitutionFactory.php │ │ │ │ │ ├── ItemPrototypeReconstitutionFactory.php │ │ │ │ │ └── ItemReconstitutionFactory.php │ │ │ │ └── Repositories/ │ │ │ │ ├── InventoryRepository.php │ │ │ │ ├── ItemPrototypeRepository.php │ │ │ │ └── ItemRepository.php │ │ │ └── UI/ │ │ │ └── Http/ │ │ │ └── CommandMappers/ │ │ │ ├── AddItemToInventoryCommandMapper.php │ │ │ ├── CreateItemCommandMapper.php │ │ │ └── EquipItemCommandMapper.php │ │ ├── Generic/ │ │ │ └── Domain/ │ │ │ ├── BaseId.php │ │ │ ├── Container/ │ │ │ │ ├── ContainerIsFullException.php │ │ │ │ ├── ContainerSlotIsTakenException.php │ │ │ │ ├── ContainerSlotOutOfRangeException.php │ │ │ │ ├── InvalidMoneyValue.php │ │ │ │ ├── ItemNotInContainer.php │ │ │ │ ├── NotEnoughMoneyToRemove.php │ │ │ │ └── NotEnoughSpaceInContainerException.php │ │ │ ├── Container.php │ │ │ └── ContainerType.php │ │ ├── Image/ │ │ │ ├── Application/ │ │ │ │ ├── Commands/ │ │ │ │ │ └── AddImageCommand.php │ │ │ │ ├── Contracts/ │ │ │ │ │ └── ImageRepositoryInterface.php │ │ │ │ ├── Factories/ │ │ │ │ │ └── ImageFactory.php │ │ │ │ └── Services/ │ │ │ │ └── ProfilePictureService.php │ │ │ ├── Domain/ │ │ │ │ ├── Image.php │ │ │ │ ├── ImageFile.php │ │ │ │ └── ImageId.php │ │ │ ├── Infrastructure/ │ │ │ │ └── Repositories/ │ │ │ │ └── ImageRepository.php │ │ │ └── UI/ │ │ │ └── Http/ │ │ │ └── CommandMappers/ │ │ │ └── AddImageCommandMapper.php │ │ ├── Level/ │ │ │ ├── Application/ │ │ │ │ └── Services/ │ │ │ │ └── LevelService.php │ │ │ └── Domain/ │ │ │ └── Level.php │ │ ├── Message/ │ │ │ ├── Application/ │ │ │ │ ├── Commands/ │ │ │ │ │ └── SendMessageCommand.php │ │ │ │ ├── Contracts/ │ │ │ │ │ └── MessageRepositoryInterface.php │ │ │ │ └── Services/ │ │ │ │ └── MessageService.php │ │ │ ├── Domain/ │ │ │ │ ├── Message.php │ │ │ │ └── MessageId.php │ │ │ ├── Infrastructure/ │ │ │ │ └── Repositories/ │ │ │ │ └── MessageRepository.php │ │ │ └── UI/ │ │ │ └── Http/ │ │ │ └── CommandMappers/ │ │ │ └── SendMessageCommandMapper.php │ │ ├── Trade/ │ │ │ ├── Application/ │ │ │ │ ├── Commands/ │ │ │ │ │ ├── AddItemToStoreCommand.php │ │ │ │ │ ├── BuyItemCommand.php │ │ │ │ │ ├── ChangeItemPriceCommand.php │ │ │ │ │ ├── CreateStoreCommand.php │ │ │ │ │ ├── MoveItemToContainerCommand.php │ │ │ │ │ ├── MoveMoneyToContainerCommand.php │ │ │ │ │ └── SellItemCommand.php │ │ │ │ ├── Contracts/ │ │ │ │ │ └── StoreRepositoryInterface.php │ │ │ │ └── Services/ │ │ │ │ ├── CreateStoreService.php │ │ │ │ ├── ManageStoreService.php │ │ │ │ └── TradeService.php │ │ │ ├── Domain/ │ │ │ │ ├── Exception/ │ │ │ │ │ └── SellPriceIsTooHigh.php │ │ │ │ ├── Store/ │ │ │ │ │ └── StoreDoesNotBuyItems.php │ │ │ │ ├── Store.php │ │ │ │ ├── StoreId.php │ │ │ │ └── StoreType.php │ │ │ ├── Infrastructure/ │ │ │ │ ├── ReconstitutionFactories/ │ │ │ │ │ └── StoreReconstitutionFactory.php │ │ │ │ └── Repositories/ │ │ │ │ └── StoreRepository.php │ │ │ └── UI/ │ │ │ └── Http/ │ │ │ └── CommandMappers/ │ │ │ ├── BuyItemCommandMapper.php │ │ │ ├── ChangeItemPriceCommandMapper.php │ │ │ ├── MoveItemToContainerCommandMapper.php │ │ │ ├── MoveMoneyToContainerCommandMapper.php │ │ │ └── SellItemCommandMapper.php │ │ └── User/ │ │ ├── Application/ │ │ │ ├── Commands/ │ │ │ │ └── CreateUserCommand.php │ │ │ └── Services/ │ │ │ └── UserService.php │ │ └── UI/ │ │ └── Http/ │ │ └── CommandMappers/ │ │ └── CreateUserCommandMapper.php │ ├── Providers/ │ │ ├── AppServiceProvider.php │ │ ├── AuthServiceProvider.php │ │ ├── BroadcastServiceProvider.php │ │ ├── EventServiceProvider.php │ │ ├── RouteServiceProvider.php │ │ └── ViewComposerServiceProvider.php │ ├── Support/ │ │ └── helpers.php │ └── Traits/ │ ├── GeneratesUuid.php │ ├── ThrowsDice.php │ └── UsesStringId.php ├── artisan ├── bootstrap/ │ ├── app.php │ └── cache/ │ └── .gitignore ├── composer.json ├── config/ │ ├── app.php │ ├── auth.php │ ├── broadcasting.php │ ├── cache.php │ ├── cors.php │ ├── database.php │ ├── filesystems.php │ ├── hashing.php │ ├── hooks.php │ ├── image.php │ ├── logging.php │ ├── mail.php │ ├── queue.php │ ├── services.php │ ├── session.php │ ├── view.php │ ├── voyager-hooks.php │ └── voyager.php ├── database/ │ ├── .gitignore │ ├── factories/ │ │ └── UserFactory.php │ ├── migrations/ │ │ ├── 2014_10_12_000000_create_users_table.php │ │ ├── 2014_10_12_100000_create_password_resets_table.php │ │ ├── 2017_05_14_055744_create_locations_table.php │ │ ├── 2017_05_14_055823_create_races_table.php │ │ ├── 2017_05_14_055844_create_characters_table.php │ │ ├── 2017_05_16_144929_create_battles_table.php │ │ ├── 2017_05_16_181330_create_battle_rounds_table.php │ │ ├── 2017_05_16_181844_create_battle_turns_table.php │ │ ├── 2017_11_26_013050_add_user_role_relationship.php │ │ ├── 2018_06_24_132346_create_messages_table.php │ │ ├── 2018_11_19_202701_create_images.php │ │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ │ ├── 2019_08_31_182034_create_items_table.php │ │ ├── 2020_02_29_205331_create_inventories.php │ │ ├── 2020_02_29_205957_create_inventory_item.php │ │ ├── 2020_03_30_133100_create_stores.php │ │ └── 2020_03_30_133448_create_store_item.php │ └── seeders/ │ ├── CharacterSeeder.php │ ├── DataRowsTableSeeder.php │ ├── DataTypesTableSeeder.php │ ├── DatabaseSeeder.php │ ├── MenuItemsTableSeeder.php │ ├── MenusTableSeeder.php │ ├── PermissionRoleTableSeeder.php │ ├── PermissionsTableSeeder.php │ ├── RolesTableSeeder.php │ ├── SettingsTableSeeder.php │ ├── TranslationsTableSeeder.php │ ├── UserSeeder.php │ ├── VoyagerDatabaseSeeder.php │ └── VoyagerDummyDatabaseSeeder.php ├── docker/ │ ├── cron/ │ │ ├── Dockerfile │ │ └── scheduler │ ├── mysql/ │ │ └── my.cnf │ ├── nginx/ │ │ └── conf.d/ │ │ └── app.conf │ └── php/ │ ├── Dockerfile │ ├── application-init.sh │ └── local.ini ├── docker-compose.yml ├── docs/ │ ├── docker_environment.md │ └── local_environment.md ├── hooks/ │ └── hooks.json ├── package.json ├── phpunit.xml ├── public/ │ ├── .htaccess │ ├── index.php │ ├── js/ │ │ ├── character-create.js │ │ ├── character-update.js │ │ └── vcountdown.js │ ├── robots.txt │ └── web.config ├── readme.md ├── resources/ │ ├── js/ │ │ ├── app.js │ │ ├── bootstrap.js │ │ └── components/ │ │ ├── FlashMessages.vue │ │ ├── InventoryManagement.vue │ │ ├── PopupModal.vue │ │ ├── StoreManagement.vue │ │ └── StoreTrade.vue │ ├── lang/ │ │ └── en/ │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php │ ├── sass/ │ │ ├── _variables.scss │ │ ├── app.scss │ │ └── custom.scss │ └── views/ │ ├── auth/ │ │ ├── login.blade.php │ │ ├── passwords/ │ │ │ ├── email.blade.php │ │ │ └── reset.blade.php │ │ └── register.blade.php │ ├── base.blade.php │ ├── battle/ │ │ ├── partials/ │ │ │ └── no-battles.blade.php │ │ └── show.blade.php │ ├── character/ │ │ ├── battle/ │ │ │ └── index.blade.php │ │ ├── create.blade.php │ │ ├── inventory/ │ │ │ └── index.blade.php │ │ ├── message/ │ │ │ └── index.blade.php │ │ ├── partials/ │ │ │ ├── actions.blade.php │ │ │ ├── attributes.blade.php │ │ │ ├── character-display.blade.php │ │ │ ├── equipment-item-mutable.blade.php │ │ │ ├── equipment-item.blade.php │ │ │ ├── equipment-mutable.blade.php │ │ │ ├── equipment.blade.php │ │ │ ├── general.blade.php │ │ │ ├── inventory.blade.php │ │ │ └── statistics.blade.php │ │ └── show.blade.php │ ├── components/ │ │ ├── increment_attribute_button.blade.php │ │ └── short_character_description.blade.php │ ├── emails/ │ │ └── password.blade.php │ ├── errors/ │ │ └── 503.blade.php │ ├── location/ │ │ ├── partials/ │ │ │ ├── list-character.blade.php │ │ │ └── navigator.blade.php │ │ └── show.blade.php │ ├── message/ │ │ ├── index.blade.php │ │ └── partials/ │ │ ├── conversation-card.blade.php │ │ ├── conversation-message.blade.php │ │ ├── conversation.blade.php │ │ ├── my-message.blade.php │ │ ├── no-messages.blade.php │ │ └── others-message.blade.php │ ├── pages/ │ │ └── index.blade.php │ ├── partials/ │ │ ├── flash-messages.blade.php │ │ └── navbar.blade.php │ ├── trade/ │ │ ├── own_store/ │ │ │ └── index.blade.php │ │ └── store/ │ │ └── index.blade.php │ └── vendor/ │ ├── .gitkeep │ └── pagination/ │ ├── bootstrap-4.blade.php │ ├── default.blade.php │ ├── semantic-ui.blade.php │ ├── simple-bootstrap-4.blade.php │ └── simple-default.blade.php ├── routes/ │ ├── api.php │ ├── channels.php │ ├── console.php │ └── web.php ├── scheduler.bat ├── server.php ├── storage/ │ ├── app/ │ │ └── public/ │ │ └── .gitignore │ ├── debugbar/ │ │ └── .gitignore │ ├── framework/ │ │ ├── .gitignore │ │ ├── cache/ │ │ │ └── .gitignore │ │ ├── sessions/ │ │ │ └── .gitignore │ │ ├── testing/ │ │ │ └── .gitignore │ │ └── views/ │ │ └── .gitignore │ └── logs/ │ └── .gitignore ├── tests/ │ ├── Browser/ │ │ ├── ExampleTest.php │ │ ├── Pages/ │ │ │ ├── HomePage.php │ │ │ └── Page.php │ │ ├── console/ │ │ │ └── .gitignore │ │ └── screenshots/ │ │ └── .gitignore │ ├── CreatesApplication.php │ ├── DuskTestCase.php │ ├── Feature/ │ │ ├── AttackTest.php │ │ ├── CharacterCreationTest.php │ │ └── ExampleTest.php │ ├── TestCase.php │ └── Unit/ │ ├── ExampleTest.php │ └── app/ │ └── Modules/ │ ├── Equipment/ │ │ ├── Application/ │ │ │ └── Services/ │ │ │ └── ItemServiceTest.php │ │ └── Domain/ │ │ └── InventoryTest.php │ └── Level/ │ ├── Application/ │ │ └── Services/ │ │ └── LevelServiceTest.php │ └── Domain/ │ └── Entities/ │ └── LevelTest.php └── webpack.mix.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto *.css linguist-vendored *.scss linguist-vendored *.js linguist-vendored CHANGELOG.md export-ignore # Files to keep with LF endings, even on Windows *.sh text eol=lf /docker/cron/scheduler eol=lf /docker/mysql/my.cnf eol=lf /docker/nginx/conf.d/app.conf eol=lf /docker/php/local.ini eol=lf ================================================ FILE: .gitignore ================================================ /node_modules /public/hot /public/storage /public/css/app.css /public/fonts/ /public/js/app.js /storage/*.key /vendor /.idea .env .phpunit.result.cache docker-compose.override.yml Homestead.json Homestead.yaml npm-debug.log yarn-error.log /.vagrant /public/mix-manifest.json ================================================ FILE: .styleci.yml ================================================ php: preset: laravel disabled: - no_unused_imports finder: not-name: - index.php - server.php js: finder: not-name: - webpack.mix.js css: true ================================================ FILE: .travis.yml ================================================ language: php php: - 7.3 addons: chrome: stable install: - cp .env.example .env - travis_retry composer install --no-interaction --prefer-dist --no-suggest - php artisan key:generate - php artisan dusk:chrome-driver before_script: - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & - touch database/database.sqlite - php artisan serve & script: - vendor/bin/phpunit - php artisan dusk ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mchekin@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Thanks for your interest in the project. Your contribution is highly appreciated. Here are our guidelines to contributing to the project. ## Adding Feature / Improvement suggestions / Submitting bug report Feel free to suggest new features, propose improvements or submitting bug reports. - Open Issues tab - Create new Issue with an appropriate label Event better if you create a pull request. ## Creating a pull request - Fork the repository. - Create a new branch from the master branch: - `git checkout -b brief-description` - Commit and push your changes. - Submit a pull request through. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 Michael Chekin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: app/Console/Kernel.php ================================================ call(function () { Character::query() ->whereColumn('hit_points', '<', 'total_hit_points') ->increment('hit_points'); })->everyMinute(); } /** * Register the commands for the application. * * @return void */ protected function commands() { $this->load(__DIR__.'/Commands'); require base_path('routes/console.php'); } } ================================================ FILE: app/Exceptions/Handler.php ================================================ reportable(function (Throwable $e) { // }); } /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Throwable $exception * @return \Symfony\Component\HttpFoundation\Response * * @throws \Throwable */ public function render($request, Throwable $exception) { if ($exception instanceof PostTooLargeException) { return back() ->withErrors([ 'message' => "The file may not be greater than {$this->getMaxSizeInKiloBytes()} kilobytes", ]); } return parent::render($request, $exception); } private function getMaxSizeInKiloBytes(): float { return bytes_to_kilobytes(config('filesystems.max_size_in_bytes')); } } ================================================ FILE: app/Http/Controllers/Api/ManageInventoryController.php ================================================ inventoryService = $inventoryService; } public function equipItem(Request $request, EquipItemCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->inventoryService->equipItem($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error equipping item: ' . $exception->getMessage() ], 500); } return response()->json(['message' => 'Item equipped']); } public function unEquipItem(Request $request, EquipItemCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->inventoryService->unEquipItem($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error un-equipping item: ' . $exception->getMessage() ], 500); } return response()->json(['message' => 'Item un-equipped']); } } ================================================ FILE: app/Http/Controllers/Api/ManageStoreController.php ================================================ service = $service; } public function changeItemPrice(Request $request, ChangeItemPriceCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->service->changeItemPrice($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error changing item price: ' . $exception->getMessage() ], 500); } return response()->json(['message' => 'Item price changed']); } public function moveItemToStore(Request $request, MoveItemToContainerCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->service->moveItemToStore($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error moving item to store: ' . $exception->getMessage() ], 500); } return response()->json(['message' => 'Item moved to store']); } public function moveItemToInventory(Request $request, MoveItemToContainerCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->service->moveItemToInventory($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error moving item to inventory: ' . $exception->getMessage() ], 500); } return response()->json(['message' => 'Item moved to inventory']); } public function moveMoneyToStore(Request $request, MoveMoneyToContainerCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->service->moveMoneyToStore($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error moving money to store: ' . $exception->getMessage() ]); } return response()->json(['message' => 'Money moved to store']); } public function moveMoneyToInventory(Request $request, MoveMoneyToContainerCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->service->moveMoneyToInventory($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error moving money to inventory: ' . $exception->getMessage() ]); } return response()->json(['status' => 'Money moved to inventory']); } } ================================================ FILE: app/Http/Controllers/Api/TradeController.php ================================================ service = $service; } public function buyItem(Request $request, BuyItemCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->service->buyItem($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error buying item: ' . $exception->getMessage() ], 500); } return response()->json(['message' => 'Item bought']); } public function sellItem(Request $request, SellItemCommandMapper $commandMapper): JsonResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->service->sellItem($command); }); } catch (Exception $exception) { return response()->json([ 'message' => 'Error selling item: ' . $exception->getMessage() ], 500); } return response()->json(['message' => 'Item bought']); } } ================================================ FILE: app/Http/Controllers/Auth/ConfirmPasswordController.php ================================================ middleware('auth'); } } ================================================ FILE: app/Http/Controllers/Auth/ForgotPasswordController.php ================================================ middleware('guest')->except('logout'); } } ================================================ FILE: app/Http/Controllers/Auth/RegisterController.php ================================================ middleware('guest'); $this->userService = $userService; $this->mapper = $mapper; } /** * Get a validator for an incoming registration request. * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:8|confirmed', ]); } /** * Create a new user instance after a valid registration. * * @param array $data * * @return User */ protected function create(array $data) { $request = $this->mapper->map($data); return $this->userService->create($request); } } ================================================ FILE: app/Http/Controllers/Auth/ResetPasswordController.php ================================================ middleware('auth'); $this->middleware('signed')->only('verify'); $this->middleware('throttle:6,1')->only('verify', 'resend'); } } ================================================ FILE: app/Http/Controllers/BattleController.php ================================================ middleware('auth', ['only' => ['show']]); $this->middleware('has.character', ['only' => ['show']]); } public function show(string $battleId) { $battle = Battle::query()->findOrFail($battleId); return view('battle.show', compact('battle')); } } ================================================ FILE: app/Http/Controllers/CharacterBattleController.php ================================================ findOrFail($characterId); return view('character.battle.index', compact('character')); } } ================================================ FILE: app/Http/Controllers/CharacterController.php ================================================ middleware('auth'); $this->middleware('has.character', ['except' => ['create', 'store', 'update']]); $this->middleware('owns.character', ['only' => ['update']]); $this->middleware('no.character', ['only' => ['create', 'store']]); $this->middleware('can.move.to.location', ['only' => ['move']]); $this->middleware('can.attack', ['only' => ['attack']]); $this->characterService = $characterService; } public function create(): View { $races = Race::all(); $user = Auth::user(); return view('character.create', compact('races', 'user')); } public function store( CreateCharacterRequest $request, CreateCharacterCommandMapper $commandMapper ): Response { $createCharacterCommand = $commandMapper->map($request); $character = $this->characterService->create($createCharacterCommand); return redirect()->route('character.show', ['character' => $character->getId()->toString()]); } public function show(string $characterId): View { $character = Character::query()->findOrFail($characterId); return view('character.show', compact('character')); } public function update( UpdateCharacterAttributeRequest $request, IncreaseAttributeCommandMapper $commandMapper, string $characterId ): Response { $increaseAttributeCommand = $commandMapper->map($characterId, $request); $this->characterService->increaseAttribute($increaseAttributeCommand); return back()->with('status', ucfirst($increaseAttributeCommand->getAttribute()) . ' + 1'); } public function move( MoveCharacterCommandMapper $commandMapper, string $characterId, string $locationId ): Response { $moveCharacterCommand = $commandMapper->map($characterId, $locationId); $this->characterService->move($moveCharacterCommand); return redirect()->route('location.show', $locationId); } public function attack( string $defenderId, Request $request, AttackCharacterCommandMapper $commandMapper ): Response { $attackCharacterCommand = $commandMapper->map($request, $defenderId); $battleId = $this->characterService->attack($attackCharacterCommand); return redirect()->route('battle.show', $battleId->toString()); } } ================================================ FILE: app/Http/Controllers/CharacterMessageController.php ================================================ middleware(['auth', 'has.character']); } public function index(string $characterId) { $character = Character::query()->findOrFail($characterId); return view('character.message.index', compact('character')); } public function store( string $characterId, Request $request, SendMessageCommandMapper $commandMapper, MessageService $messageService ) { $sendMessageCommand = $commandMapper->map($request); $messageService->send($sendMessageCommand); return redirect()->route('character.message.index', $characterId); } } ================================================ FILE: app/Http/Controllers/CharacterStoreController.php ================================================ middleware('auth'); $this->middleware('has.character'); } public function index(Request $request, string $characterId): View { /** @var Character $customer */ $customer = $request->user()->character; /** @var Character $trader */ $trader = Character::query()->findOrFail($characterId); return view('trade.store.index', compact('customer', 'trader')); } } ================================================ FILE: app/Http/Controllers/Controller.php ================================================ middleware('auth'); $this->inventoryService = $inventoryService; } public function index(Request $request, LevelService $levelService): View { /** @var Character $character */ $character = $request->user()->character; $level = $levelService->getLevel($character->getLevelNumber()); return view('character.inventory.index', ['character' => $character, 'level' => $level]); } public function equipItem(Request $request, EquipItemCommandMapper $commandMapper): RedirectResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->inventoryService->equipItem($command); }); } catch (Exception $exception) { return redirect()->back()->withErrors([ 'message' => 'Error equipping item' ]); } return redirect()->back()->with('status', 'Item equipped'); } public function unEquipItem(Request $request, EquipItemCommandMapper $commandMapper): RedirectResponse { $command = $commandMapper->map($request); try { DB::transaction(function () use ($command) { $this->inventoryService->unEquipItem($command); }); } catch (Exception $exception) { return redirect()->back()->withErrors([ 'message' => 'Error un-equipping item' ]); } return redirect()->back()->with('status', 'Item un-equipped'); } } ================================================ FILE: app/Http/Controllers/ItemCreateController.php ================================================ middleware('auth'); $this->middleware('has.character'); $this->middleware('is.admin'); $this->itemService = $itemService; } public function store( CreateItemRequest $request, CreateItemCommandMapper $commandMapper ): Response { $createItemCommand = $commandMapper->map($request); $this->itemService->create($createItemCommand); return redirect()->back(); } } ================================================ FILE: app/Http/Controllers/LocationController.php ================================================ middleware('auth', ['only' => ['show']]); $this->middleware('has.character', ['only' => ['show']]); $this->middleware('character.location', ['only' => ['show']]); } public function show(string $locationId) { /** @var Location $location */ $location = Location::query() ->with('characters.race') ->with('characters.user') ->with('adjacentLocations') ->findOrFail($locationId); return view('location.show', compact('location')); } } ================================================ FILE: app/Http/Controllers/MessageController.php ================================================ middleware(['auth', 'has.character']); } public function index(): View { return view('message.index'); } } ================================================ FILE: app/Http/Controllers/OwnStoreController.php ================================================ middleware('auth'); $this->middleware('has.character'); } public function index(Request $request): View { /** @var Character $character */ $character = $request->user()->character; return view('trade.own_store.index', compact('character')); } } ================================================ FILE: app/Http/Controllers/ProfilePictureController.php ================================================ middleware('owns.character'); } public function store( string $characterId, UploadImageRequest $request, ProfilePictureService $profilePictureService, AddImageCommandMapper $commandMapper ) { $addImageCommand = $commandMapper->map($characterId, $request->file('file')); $profilePictureService->update($addImageCommand); return back()->with('status', 'Profile picture has been changed'); } public function destroy( string $characterId, ProfilePictureService $profilePictureService ) { $profilePictureService->delete(CharacterId::fromString($characterId)); return back()->with('status', 'Profile picture has been deleted'); } } ================================================ FILE: app/Http/Kernel.php ================================================ [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; /** * The application's route middleware. * * These middleware may be assigned to groups or used individually. * * @var array */ protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'character.location' => \App\Http\Middleware\IsCharacterLocation::class, 'has.character' => \App\Http\Middleware\HasCharacter::class, 'can.move.to.location' => \App\Http\Middleware\CanMoveToLocation::class, 'can.attack' => \App\Http\Middleware\CanAttack::class, 'no.character' => \App\Http\Middleware\NoCharacterYet::class, 'owns.character' => \App\Http\Middleware\UserOwnsCharacter::class, 'is.admin' => \App\Http\Middleware\IsAdmin::class, ]; } ================================================ FILE: app/Http/Middleware/Authenticate.php ================================================ expectsJson()) { return route('login'); } } } ================================================ FILE: app/Http/Middleware/CanAttack.php ================================================ findOrFail($request->route('character')); /** @var User $user */ $user = $request->user(); $currentUserCharacter = $user->getCharacter(); if (!$currentUserCharacter->isAlive()) { return redirect()->back()->withErrors([ 'message' => 'You cannot attack when your character is knocked out', ]); } if (!$targetCharacter->isAlive()) { return redirect()->back()->withErrors([ 'message' => 'You cannot attack a knocked out character', ]); } if ($targetCharacter->getId() === $currentUserCharacter->getId()) { return redirect()->back()->withErrors([ 'message' => 'You cannot attack yourself', ]); } return $next($request); } } ================================================ FILE: app/Http/Middleware/CanMoveToLocation.php ================================================ findOrFail($request->route('character')); /** @var Location $location */ $location = Location::query()->findOrFail($request->route('location')); /** @var Location $characterLocation */ $characterLocation = $character->location; // if this character does not belong to the logged in user if (Auth::user()->id !== $character->user->id || !$characterLocation->isAdjacentLocation($location)) { return redirect()->route('location.show', $location->getId()); } return $next($request); } } ================================================ FILE: app/Http/Middleware/EncryptCookies.php ================================================ user(); if ($user && !$user->hasCharacter()) { return redirect()->route('character.create'); } return $next($request); } } ================================================ FILE: app/Http/Middleware/IsAdmin.php ================================================ user(); if ($user && $user->hasRole('admin')) { return redirect()->back(); } return $next($request); } } ================================================ FILE: app/Http/Middleware/IsCharacterLocation.php ================================================ user(); $locationId = $user->character->getLocationId(); if ($user && $user->hasCharacter() && $locationId !== $request->route('location')) { return redirect()->route('location.show', $locationId); } return $next($request); } } ================================================ FILE: app/Http/Middleware/NoCharacterYet.php ================================================ user(); if ($user && $user->hasCharacter()) { return redirect('/home'); } return $next($request); } } ================================================ FILE: app/Http/Middleware/PreventRequestsDuringMaintenance.php ================================================ check()) { return redirect(RouteServiceProvider::HOME); } } return $next($request); } } ================================================ FILE: app/Http/Middleware/TrimStrings.php ================================================ allSubdomainsOfApplicationUrl(), ]; } } ================================================ FILE: app/Http/Middleware/TrustProxies.php ================================================ user(); if($user) { $user->updateLastUserActivity(); } return $next($request); } } ================================================ FILE: app/Http/Middleware/UserOwnsCharacter.php ================================================ user(); /** @var Character $character */ $character = Character::query()->findOrFail($request->route('character')); if ($user && !$user->hasThisCharacter($character)) { return redirect()->back(); } return $next($request); } } ================================================ FILE: app/Http/Middleware/VerifyCsrfToken.php ================================================ back()->withErrors([ 'message' => 'Your Session have expired. Try again.', ]); } } } ================================================ FILE: app/Http/Requests/CreateCharacterRequest.php ================================================ 'required|unique:characters,name|min:2', 'gender' => 'required|in:male,female', 'race_id' => 'required|integer', ]; } } ================================================ FILE: app/Http/Requests/CreateItemRequest.php ================================================ 'required', ]; } } ================================================ FILE: app/Http/Requests/UpdateCharacterAttributeRequest.php ================================================ 'required|in:strength,agility,constitution,intelligence,charisma', ]; } } ================================================ FILE: app/Http/Requests/UploadImageRequest.php ================================================ [ 'required', 'image', 'mimes:jpeg,png,gif', 'max:' . bytes_to_kilobytes(config('filesystems.max_size_in_bytes')) ], ]; } } ================================================ FILE: app/Http/ViewComposers/BattlesComposer.php ================================================ getData(); /** @var Character $character */ $character = Arr::get($data, 'character'); /** @var Collection $battles */ $battles = Battle::query()->where(function (Builder $query) use ($character) { $query->where([ 'attacker_id' => $character->id, ]); })->orWhere(function (Builder $query) use ($character) { $query->where([ 'defender_id' => $character->id, ]); })->orderByDesc('created_at')->paginate(10); $unseenBattles = $character->defends()->unseenByDefender()->whereIn('id', $battles->pluck('id')); $unseenBattles->markAsSeenByDefender(); $view->with(compact('character', 'battles')); } } ================================================ FILE: app/Http/ViewComposers/CharacterGeneralInfoComposer.php ================================================ levelService = $levelService; } /** * Bind data to the view. * * @param View $view * @return void */ public function compose(View $view) { $data = $view->getData(); /** @var Character $character */ $character = Arr::get($data, 'character'); $level = $this->levelService->getLevel($character->getLevelNumber()); $view->with(compact('character', 'level')); } } ================================================ FILE: app/Http/ViewComposers/CharacterMessagesComposer.php ================================================ getData(); /** @var Character $currentCharacter */ /** @var Character $otherCharacter */ $currentCharacter = Auth::user()->character; $otherCharacter = Arr::get($data, 'character'); $messages = Message::query()->where(function (Builder $query) use ($currentCharacter, $otherCharacter) { $query->where([ 'to_id' => $currentCharacter->id, 'from_id' => $otherCharacter->id, ]); })->orWhere(function (Builder $query) use ($currentCharacter, $otherCharacter) { $query->where([ 'to_id' => $otherCharacter->id, 'from_id' => $currentCharacter->id, ]); })->orderByDesc('created_at')->paginate(5); $otherCharacter->sentMessages()->whereIn('id', $messages->pluck('id'))->markAsRead(); $contentLimit = Message::CONTENT_LIMIT; $view->with(compact('messages', 'currentCharacter', 'otherCharacter', 'contentLimit')); } } ================================================ FILE: app/Http/ViewComposers/MessagesComposer.php ================================================ character; $builder = Message::query()->whereIn('auto_id', function (Builder $query) use ($currentCharacter) { $query ->select(DB::raw('max(`auto_id`)')) ->from('messages') ->where(function (Builder $query) use ($currentCharacter) { $query->where([ 'to_id' => $currentCharacter->id, ]); })->orWhere(function (Builder $query) use ($currentCharacter) { $query->where([ 'from_id' => $currentCharacter->id, ]); })->groupBy(DB::raw(' CASE WHEN from_id = "' . $currentCharacter->id . '" THEN to_id ELSE from_id END')); })->orderByDesc('auto_id'); $messages = $builder->paginate(5); $view->with(compact('messages', 'currentCharacter')); } } ================================================ FILE: app/Models/Battle.php ================================================ hasMany(BattleRound::class); } public function attacker(): BelongsTo { return $this->belongsTo(Character::class, 'attacker_id'); } public function defender(): BelongsTo { return $this->belongsTo(Character::class, 'defender_id'); } public function victor(): BelongsTo { return $this->belongsTo(Character::class, 'victor_id'); } public function location(): BelongsTo { return $this->belongsTo(Location::class); } /** * @param $query * @return mixed */ public function scopeUnseenByDefender($query) { return $query->where('seen_by_defender', false); } /** * Read the selected Messages * * @param $query * @return mixed */ public function scopeMarkAsSeenByDefender($query) { return $query->update(['seen_by_defender' => true]); } public function getAttacker(): Character { return $this->attacker; } public function getDefender(): Character { return $this->defender; } public function isTheVictor(Character $character): bool { return $this->victor->id === $character->getId(); } } ================================================ FILE: app/Models/BattleRound.php ================================================ hasMany(BattleTurn::class); } } ================================================ FILE: app/Models/BattleTurn.php ================================================ belongsTo(Character::class, 'executor_id'); } /** * @return BelongsTo */ public function target() { return $this->belongsTo(Character::class, 'target_id'); } } ================================================ FILE: app/Models/Character.php ================================================ belongsTo(User::class); } public function race(): BelongsTo { return $this->belongsTo(Race::class); } public function location(): BelongsTo { return $this->belongsTo(Location::class); } public function profilePicture(): BelongsTo { return $this->belongsTo(Image::class, 'profile_picture_id'); } public function images(): HasMany { return $this->hasMany(Image::class); } public function inventory(): HasOne { return $this->hasOne(Inventory::class); } public function store(): HasOne { return $this->hasOne(Store::class); } public function receivedMessages(): HasMany { return $this->hasMany(Message::class, 'to_id'); } public function sentMessages(): HasMany { return $this->hasMany(Message::class, 'from_id'); } public function attacks(): HasMany { return $this->hasMany(Battle::class, 'attacker_id'); } public function defends(): HasMany { return $this->hasMany(Battle::class, 'defender_id'); } public function battles(): HasMany { return $this->hasMany(Battle::class, 'defender_id'); } public function sendMessageTo(Character $companion, string $content): Character { $this->sentMessages()->create([ 'to_id' => $companion->getId(), 'content' => $content, ]); return $this; } public function isYou(): bool { return $this->isPlayerCharacter() && $this->user->isCurrentAuthenticatedUser(); } public function isOwnMerchant(): bool { return $this->isMerchant() && $this->user->isCurrentAuthenticatedUser(); } public function isPlayerCharacter(): bool { return $this->type === CharacterType::PLAYER; } public function isMerchant(): bool { return $this->type === CharacterType::MERCHANT; } public function isMonster(): bool { return $this->type === CharacterType::MONSTER; } public function isNPC(): bool { return !$this->isPlayerCharacter(); } public function hasProfilePicture(): bool { return $this->profilePicture()->exists(); } public function isOnline(): bool { if($this->isNPC()) { return true; } return $this->user->isOnline(); } public function getProfilePicture(): Image { return $this->profilePicture; } public function getProfilePictureFull(): string { if ($this->profilePicture()->exists()) { /** @var Image $image */ $image = $this->profilePicture()->first(); return $image->getFilePathFull(); } return $this->race->getImageByGender($this->gender); } public function getProfilePictureSmall(): string { if ($this->profilePicture()->exists()) { /** @var Image $image */ $image = $this->profilePicture()->first(); return $image->getFilePathSmall(); } return 'svg/avatar.svg'; } public function getProfilePictureId(): ?string { return $this->profile_picture_id; } public function getRaceName(): string { return $this->race->getName(); } public function getLevelNumber():int { return $this->level_id; } public function getLocationName():string { return $this->location->getName(); } public function getId(): string { return $this->id; } public function isAlive(): bool { return $this->hit_points > 0; } public function getStrength(): int { return $this->strength; } public function getAgility(): int { return $this->agility; } public function getConstitution(): int { return $this->constitution; } public function getIntelligence(): int { return $this->intelligence; } public function getCharisma(): int { return $this->charisma; } public function getLocationId(): string { return $this->location_id; } public function getHitPoints(): int { return $this->hit_points; } public function getTotalHitPoints(): int { return $this->total_hit_points; } public function getUserId() { return $this->user ? $this->user->getId() : null; } public function getName(): string { return $this->name; } public function getGender(): string { return $this->gender; } public function getType(): string { return $this->type; } public function getRaceId(): int { return $this->race->getId(); } public function getXp(): int { return $this->xp; } public function getAvailableAttributePoints(): int { return $this->available_attribute_points; } public function getBattlesLost(): int { return $this->battles_lost; } public function getBattlesWon(): int { return $this->battles_won; } public function getHeadGearItem() { return $this->inventory->items() ->where('type', ItemType::HEAD_GEAR) ->wherePivot('status', ItemStatus::EQUIPPED) ->first() ; } public function getBodyArmorItem() { return $this->inventory->items() ->where('type', ItemType::BODY_ARMOR) ->wherePivot('status', ItemStatus::EQUIPPED) ->first() ; } public function getMainHandItem() { return $this->inventory->items() ->where('type', ItemType::MAIN_HAND) ->wherePivot('status', ItemStatus::EQUIPPED) ->first() ; } public function getOffHandItem() { return $this->inventory->items() ->where('type', ItemType::OFF_HAND) ->wherePivot('status', ItemStatus::EQUIPPED) ->first() ; } } ================================================ FILE: app/Models/Image.php ================================================ id; } public function getCharacterId(): string { return $this->character_id; } public function getFilePathFull(): string { return $this->file_path_full; } public function getFilePathSmall(): string { return $this->file_path_small; } public function getFilePathIcon(): string { return $this->file_path_icon; } } ================================================ FILE: app/Models/Inventory.php ================================================ 'integer', ]; protected $guarded = []; public function character(): BelongsTo { return $this->belongsTo(Character::class, 'character_id'); } public function items(): BelongsToMany { return $this->belongsToMany(Item::class)->withPivot('inventory_slot_number', 'status'); } public function getId(): string { return $this->id; } public function getCharacterId(): string { return $this->character_id; } public function getMoney(): int { return $this->money; } } ================================================ FILE: app/Models/Item.php ================================================ 'array' ]; public function inventory(): BelongsToMany { return $this->belongsToMany(Inventory::class); } public function prototype(): BelongsTo { return $this->belongsTo(ItemPrototype::class); } public function getId(): string { return $this->id; } public function getName(): string { return $this->name; } public function getDescription(): string { return $this->description; } public function getImageFilePath(): string { return $this->image_file_path; } public function getType(): string { return $this->type; } public function getStatus(): string { return $this->status; } public function getEffects(): array { return $this->effects; } public function getPrice(): int { return $this->price; } public function getPrototypeId(): string { return $this->prototype_id; } public function getCreatorCharacterId(): string { return $this->creator_character_id; } public function isEquipped(): bool { return $this->pivot->status === ItemStatus::EQUIPPED; } public function getInventorySlotNumber(): int { return $this->pivot->inventory_slot_number; } } ================================================ FILE: app/Models/ItemPrototype.php ================================================ 'array' ]; public function getId(): string { return $this->id; } public function getName(): string { return $this->name; } public function getDescription(): string { return $this->description; } public function getImageFilePath(): string { return $this->image_file_path; } public function getType(): string { return $this->type; } public function getEffects(): array { return $this->effects; } public function getPrice(): int { return $this->price; } } ================================================ FILE: app/Models/Location.php ================================================ 'south', 'east' => 'west', ]; /** * Getting all possible movement directions. * * @return array */ static public function getDirections() { return array_merge(array_keys(Location::$oppositeDirections), array_values(Location::$oppositeDirections)); } /** * Getting the opposite direction * * @param $direction * * @return mixed */ static protected function getAppositeDirection($direction) { if (array_key_exists ($direction, self::$oppositeDirections)) { return self::$oppositeDirections[$direction]; } if (in_array($direction, self::$oppositeDirections)) { return array_search($direction, self::$oppositeDirections); } throw new \InvalidArgumentException('Invalid direction: '.$direction); } /** * @param $direction * * @return bool */ static protected function isValidDirection($direction) { return array_key_exists ($direction, self::$oppositeDirections) || in_array($direction, self::$oppositeDirections); } /** * Get the characters at the location. * * @return HasMany */ public function characters() { return $this->hasMany(Character::class); } /** * @return BelongsToMany */ public function adjacentLocations() { return $this->belongsToMany(Location::class, 'adjacent_location', 'location_id', 'adjacent_location_id')->withPivot('direction'); } public function adjacent($type) { return $this->adjacentLocations->filter(function (Location $value, $key) use ($type) { return $value->getOriginal('pivot_direction') === $type; })->first(); } public function addAdjacentLocation(Location $adjacent, $direction) { if (!self::isValidDirection($direction)) { throw new \InvalidArgumentException('Invalid adjacent direction type: '.$direction); } $this->adjacentLocations()->attach($adjacent, [ 'direction' => $direction, "created_at" => Carbon::now(), "updated_at" => Carbon::now(), ]); // add adjacent $adjacent->adjacentLocations()->attach($this, [ 'direction' => self::getAppositeDirection($direction), "created_at" => Carbon::now(), "updated_at" => Carbon::now(), ]); // add yourself, too } public function removeAdjacentLocation(Location $adjacent) { $this->adjacentLocations()->detach($adjacent); // remove friend $adjacent->adjacentLocations()->detach($this); // remove yourself, too } public function getName(): string { return $this->name; } public function isAdjacentLocation(Location $location): bool { return (bool)$this->adjacentLocations()->where('id', $location->getId())->first(); } public function getId(): string { return $this->id; } } ================================================ FILE: app/Models/Message.php ================================================ belongsTo(Character::class, 'from_id'); } /** * @return BelongsTo */ public function recipient() { return $this->belongsTo(Character::class, 'to_id'); } /** * Get the number of new messages related to this conversation * * @param $query * @return mixed */ public function scopeUnread($query) { return $query->where('state', Message::UNREAD); } /** * Read the selected Messages * * @param $query * @return mixed */ public function scopeMarkAsRead($query) { return $query->update(['state' => Message::READ]); } /** * Set the user's first name. * * @param string $value * @return void */ public function setContentAttribute($value) { $value = str_replace("\r\n", "\n", $value); $limitedString = Str::limit($value, self::CONTENT_LIMIT, ''); $this->attributes['content'] = nl2br(e($limitedString)); } public function unseenByRecipient(): bool { return (string)$this->getOriginal('state') === self::UNREAD; } } ================================================ FILE: app/Models/Race.php ================================================ {"{$gender}_image"}; } public function getId(): int { return $this->getKey(); } public function getStartingLocationId(): string { return $this->{self::ATTRIBUTE_STARTING_LOCATION_ID}; } public function getStrength(): int { return $this->{self::ATTRIBUTE_STRENGTH}; } public function getAgility(): int { return $this->{self::ATTRIBUTE_AGILITY}; } public function getConstitution(): int { return $this->{self::ATTRIBUTE_CONSTITUTION}; } public function getIntelligence(): int { return $this->{self::ATTRIBUTE_INTELLIGENCE}; } public function getCharisma(): int { return $this->{self::ATTRIBUTE_CHARISMA}; } public function getName(): string { return $this->name; } public function getDescription(): string { return $this->description; } public function getMaleImage(): string { return $this->male_image; } public function getFemaleImage(): string { return $this->female_image; } } ================================================ FILE: app/Models/Store.php ================================================ 'integer', ]; protected $guarded = []; public function character(): BelongsTo { return $this->belongsTo(Character::class, 'character_id'); } public function items(): BelongsToMany { return $this->belongsToMany(Item::class, 'store_item') ->withPivot('inventory_slot_number', 'price'); } public function getId(): string { return $this->id; } public function getType(): string { return $this->type; } public function getCharacterId(): string { return $this->character_id; } public function getMoney(): int { return $this->money; } } ================================================ FILE: app/Models/User.php ================================================ 'datetime', ]; public function character(): HasOne { return $this->hasOne(Character::class); } public function hasCharacter(): bool { return $this->character()->getQuery()->exists(); } public function getId(): int { return $this->id; } public function isCurrentAuthenticatedUser(): bool { return $this->getId() == Auth::id(); } public function getCharacter(): Character { return $this->character; } public function hasThisCharacter(Character $character): bool { return $this->character->id === $character->getId(); } public function updateLastUserActivity(): User { $expiresAt = Carbon::now()->addMinutes(5); Cache::put('last-user-activity-' . $this->id, true, $expiresAt); return $this; } public function isOnline(): bool { return Cache::has('last-user-activity-' . $this->id); } } ================================================ FILE: app/Modules/Battle/Application/Contracts/BattleRepositoryInterface.php ================================================ id = $id; $this->locationId = $locationId; $this->attacker = $attacker; $this->defender = $defender; $this->rounds = $rounds; $this->victorXpGained = $victorXpGained; $this->victor = $victor; } public function execute(): void { do { $round = $this->createRound( $this->getAttacker(), $this->getDefender() ); $round->execute(); $this->rounds->push($round); } while ($round->notLastRound()); $this->victor = $this->attacker->isAlive() ? $this->attacker : $this->defender; $loser = $this->attacker->isAlive() ? $this->defender : $this->attacker; $this->victorXpGained = $this->calculateVictorXpGained($loser, $this->victor); } private function calculateVictorXpGained(Character $loser, Character $victor): int { return max($loser->getLevelNumber() - $victor->getLevelNumber(), 1) * 3; } public function getId(): BattleId { return $this->id; } public function getAttacker(): Character { return $this->attacker; } public function getDefender(): Character { return $this->defender; } public function getVictorXpGained(): int { return $this->victorXpGained; } public function getRounds(): BattleRounds { return $this->rounds; } public function getVictor(): Character { return $this->victor; } public function getLoser(): Character { return $this->victor->equals($this->attacker) ? $this->defender : $this->attacker; } public function getLocationId(): string { return $this->locationId; } private function createRound(Character $attacker, Character $defender): BattleRound { return new BattleRound( $this->generateUuid(), $attacker, $defender, new BattleTurns() ); } } ================================================ FILE: app/Modules/Battle/Domain/BattleId.php ================================================ id = $id; $this->attacker = $attacker; $this->defender = $defender; $this->turns = $turns; } public function getId(): string { return $this->id; } public function getTurns(): BattleTurns { return $this->turns; } public function execute(): void { $turn = $this->createTurn($this->attacker, $this->defender); $turn->execute(); $this->turns->push($turn); if ($turn->isTargetAlive()) { $turn = $this->createTurn($this->defender, $this->attacker); $turn->execute(); $this->turns->push($turn); } } public function notLastRound(): bool { /** @var BattleTurn $lastTurn */ $lastTurn = $this->turns->last(); return $lastTurn->isTargetAlive(); } private function createTurn(Character $owner, Character $target): BattleTurn { return new BattleTurn( $this->generateUuid(), $owner, $target, BattleTurnResult::none() ); } } ================================================ FILE: app/Modules/Battle/Domain/BattleRounds.php ================================================ id = $id; $this->owner = $owner; $this->target = $target; $this->result = $result; } public function execute() { if (!$this->isTargetHit()) { $this->result = BattleTurnResult::miss(); } $forceFactor = $this->owner->generateDamage(); $armorRating = $this->target->getArmorRating(); $isCriticalHit = $this->isCriticalHit(); $forceFactor = $isCriticalHit ? $forceFactor * 3 : $forceFactor; $damage = max($forceFactor - $armorRating, 0); $damageAbsorbed = $forceFactor - $damage; $this->result = $isCriticalHit ? BattleTurnResult::criticalHit($damage, $damageAbsorbed) : BattleTurnResult::hit($damage, $damageAbsorbed); $this->target->applyDamage($damage); } public function isOwnerAlive(): bool { return $this->owner->isAlive(); } public function isTargetAlive(): bool { return $this->target->isAlive(); } private function isTargetHit(): bool { $precision = $this->owner->generatePrecision(); $evasion = $this->target->generateEvasionFactor(); return $precision > $evasion; } private function isCriticalHit(): bool { $trickery = $this->owner->generateTrickery(); $awareness = $this->target->generateAwareness(); return $trickery > $awareness; } public function getId(): string { return $this->id; } public function getOwner(): Character { return $this->owner; } public function getTarget(): Character { return $this->target; } public function getDamageDone(): int { return $this->result->getDamageDone(); } public function getDamageAbsorbed(): int { return $this->result->getDamageAbsorbed(); } public function getResultType(): string { return $this->result->getType(); } } ================================================ FILE: app/Modules/Battle/Domain/BattleTurnResult.php ================================================ type = $type; $this->damageDone = $damageDone; $this->damageAbsorbed = $damageAbsorbed; } public static function none() { return new self(self::NONE, 0, 0); } public static function miss() { return new self(self::MISS, 0, 0); } public static function hit(int $damageDone, int $damageAbsorbed) { return new self(self::HIT, $damageDone, $damageAbsorbed); } public static function criticalHit(int $damageDone, int $damageAbsorbed) { return new self(self::CRITICAL_HIT, $damageDone, $damageAbsorbed); } public function getType(): string { return $this->type; } public function getDamageDone(): int { return $this->damageDone; } public function getDamageAbsorbed(): int { return $this->damageAbsorbed; } } ================================================ FILE: app/Modules/Battle/Domain/BattleTurns.php ================================================ generateUuid()); } public function add(Battle $battle): void { /** @var BattleModel $battleModel */ $battleModel = BattleModel::query()->create([ 'id' => $battle->getId()->toString(), 'location_id' => $battle->getLocationId(), 'attacker_id' => $battle->getAttacker()->getId()->toString(), 'defender_id' => $battle->getDefender()->getId()->toString(), 'victor_id' => $battle->getVictor()->getId()->toString(), 'victor_xp_gained' => $battle->getVictorXpGained(), ]); /** @var BattleRound $round */ foreach ($battle->getRounds()->all() as $round) { /** @var BattleRoundModel $roundModel */ $roundModel = $battleModel->rounds()->create([ 'id' => $round->getId(), ]); /** @var BattleTurn $turn */ foreach ($round->getTurns()->all() as $turn) { $roundModel->turns()->create([ 'id' => $turn->getId(), 'damageDone' => $turn->getDamageDone(), 'damageAbsorbed' => $turn->getDamageAbsorbed(), 'result_type' => $turn->getResultType(), 'executor_id' => $turn->getOwner()->getId()->toString(), 'target_id' => $turn->getTarget()->getId()->toString(), ]); } } } } ================================================ FILE: app/Modules/Character/Application/Commands/AttackCharacterCommand.php ================================================ attackerId = $attackerId; $this->defenderId = $defenderId; } public function getAttackerId(): CharacterId { return $this->attackerId; } public function getDefenderId(): CharacterId { return $this->defenderId; } } ================================================ FILE: app/Modules/Character/Application/Commands/CreateCharacterCommand.php ================================================ name = $name; $this->gender = $gender; $this->type = $type; $this->raceId = $raceId; $this->userId = $userId; } public function getName(): string { return $this->name; } public function getGender(): string { return $this->gender; } public function getCharacterType(): string { return $this->type; } public function getRaceId(): int { return $this->raceId; } public function getUserId(): string { return $this->userId; } } ================================================ FILE: app/Modules/Character/Application/Commands/IncreaseAttributeCommand.php ================================================ characterId = $characterId; $this->attribute = $attribute; } public function getCharacterId(): CharacterId { return $this->characterId; } public function getAttribute(): string { return $this->attribute; } } ================================================ FILE: app/Modules/Character/Application/Commands/MoveCharacterCommand.php ================================================ characterId = $characterId; $this->locationId = $locationId; } public function getCharacterId(): CharacterId { return $this->characterId; } public function getLocationId(): string { return $this->locationId; } } ================================================ FILE: app/Modules/Character/Application/Contracts/CharacterRepositoryInterface.php ================================================ getId(), 1, $race->getStartingLocationId(), $command->getName(), new Gender($command->getGender()), new CharacterType($command->getCharacterType()), 0, new Reputation(0), new Attributes([ 'strength' => $race->getStrength(), 'agility' => $race->getAgility(), 'constitution' => $race->getConstitution(), 'intelligence' => $race->getIntelligence(), 'charisma' => $race->getCharisma(), 'unassigned' => 0, ]), HitPoints::byRace($race), new Statistics([ 'battlesLost' => 0, 'battlesWon' => 0, ]), $inventory, $command->getUserId() ); } } ================================================ FILE: app/Modules/Character/Application/Services/CharacterService.php ================================================ characterFactory = $characterFactory; $this->characterRepository = $characterRepository; $this->raceRepository = $raceRepository; $this->battleRepository = $battleRepository; $this->levelService = $levelService; $this->inventoryService = $inventoryService; $this->storeService = $storeService; } public function create(CreateCharacterCommand $command): Character { $characterId = $this->characterRepository->nextIdentity(); $character = $this->characterFactory->create( $characterId, $command, $this->raceRepository->getOne($command->getRaceId()), $this->inventoryService->create(new CreateInventoryCommand($characterId)) ); $this->characterRepository->add($character); $this->storeService->create(new CreateStoreCommand($characterId)); return $character; } public function increaseAttribute(IncreaseAttributeCommand $command): void { $character = $this->characterRepository->getOne($command->getCharacterId()); $character->applyAttributeIncrease($command->getAttribute()); $this->characterRepository->update($character); } public function move(MoveCharacterCommand $command): void { $character = $this->characterRepository->getOne($command->getCharacterId()); $character->setLocationId($command->getLocationId()); $this->characterRepository->update($character); } public function attack(AttackCharacterCommand $command): BattleId { return DB::transaction(function () use ($command) { $attacker = $this->characterRepository->getOne($command->getAttackerId()); $defender = $this->characterRepository->getOne($command->getDefenderId()); $battleId = $this->battleRepository->nextIdentity(); $battle = new Battle( $battleId, $defender->getLocationId(), $attacker, $defender, new BattleRounds(), 0 ); $battle->execute(); $victor = $battle->getVictor(); $loser = $battle->getLoser(); $victor->incrementWonBattles(); $loser->incrementLostBattles(); $victor->addXp($battle->getVictorXpGained()); $newLevel = $this->levelService->getLevelByXp($victor->getXp()); $victor->updateLevel($newLevel->getId()); $this->characterRepository->update($victor); $this->characterRepository->update($loser); $this->battleRepository->add($battle); return $battleId; }); } public function updateProfilePicture(Image $picture): void { $character = $this->characterRepository->getOne($picture->getCharacterId()); $character->setProfilePictureId($picture->getId()); $this->characterRepository->update($character); } public function removeProfilePicture(CharacterId $characterId): void { $character = $this->characterRepository->getOne($characterId); $character->removeProfilePicture(); $this->characterRepository->update($character); } } ================================================ FILE: app/Modules/Character/Domain/Attributes.php ================================================ collection = new Collection($items); } public function addAvailablePoints(int $points): Attributes { $rawData = $this->collection->all(); $rawData['unassigned'] += $points; return new static($rawData); } public function assignAvailablePoint(string $attribute): Attributes { $rawData = $this->collection->all(); $rawData['unassigned']--; $rawData[$attribute]++; return new static($rawData); } public function hasAvailablePoints(): bool { return (bool)$this->collection->get('unassigned'); } public function getStrength(): int { return $this->collection->get('strength'); } public function getAgility(): int { return $this->collection->get('agility'); } public function getConstitution(): int { return $this->collection->get('constitution'); } public function getIntelligence(): int { return $this->collection->get('intelligence'); } public function getCharisma(): int { return $this->collection->get('charisma'); } public function getUnassignedAttributePoints(): int { return $this->collection->get('unassigned'); } } ================================================ FILE: app/Modules/Character/Domain/Character.php ================================================ id = $id; $this->name = $name; $this->gender = $gender; $this->type = $type; $this->levelId = $levelId; $this->raceId = $raceId; $this->locationId = $locationId; $this->xp = $xp; $this->reputation = $reputation; $this->attributes = $attributes; $this->hitPoints = $hitPoints; $this->statistics = $statistics; $this->inventory = $inventory; $this->userId = $userId; $this->profilePictureId = $profilePictureId; } public function getLevelNumber(): int { return $this->levelId; } public function getId(): CharacterId { return $this->id; } public function generateDamage(): int { return self::throwOneDice() + $this->getBaseDamage(); } public function getBaseDamage(): int { return $this->getStrength() + $this->inventory->getEquippedItemsEffect(ItemEffect::DAMAGE); } public function generatePrecision(): int { return self::throwTwoDices() + $this->getBasePrecision(); } public function getBasePrecision(): int { return $this->getAgility() + $this->inventory->getEquippedItemsEffect(ItemEffect::PRECISION); } public function generateEvasionFactor(): int { return self::throwTwoDices() + $this->getBaseEvasion(); } public function getBaseEvasion(): int { return $this->getAgility() + $this->inventory->getEquippedItemsEffect(ItemEffect::EVASION); } public function generateTrickery(): int { return self::throwOneDice() + $this->getBaseTrickery(); } public function getBaseTrickery(): int { return $this->getIntelligence() + $this->inventory->getEquippedItemsEffect(ItemEffect::TRICKERY); } public function generateAwareness(): int { return self::throwTreeDices() + $this->getBaseAwareness(); } public function getBaseAwareness(): int { return $this->getIntelligence() * 2 + $this->inventory->getEquippedItemsEffect(ItemEffect::AWARENESS); } public function getArmorRating(): int { return $this->inventory->getEquippedItemsEffect(ItemEffect::ARMOR); } public function getStrength(): int { return $this->attributes->getStrength(); } public function getAgility(): int { return $this->attributes->getAgility(); } public function getConstitution(): int { return $this->attributes->getConstitution(); } public function getIntelligence(): int { return $this->attributes->getIntelligence(); } public function getCharisma(): int { return $this->attributes->getCharisma(); } public function getUnassignedAttributePoints(): int { return $this->attributes->getUnassignedAttributePoints(); } public function getLocationId(): string { return $this->locationId; } public function getHitPoints(): int { return $this->hitPoints->getCurrentHitPoints(); } public function getTotalHitPoints(): int { return $this->hitPoints->getMaximumHitPoints(); } public function equals(Character $other): bool { return $this->getId()->equals($other->getId()); } public function getName(): string { return $this->name; } public function getUserId(): ?int { return $this->userId; } public function getGender(): Gender { return $this->gender; } public function getType(): CharacterType { return $this->type; } public function getXp(): int { return $this->xp; } public function getRaceId(): int { return $this->raceId; } public function getMoney(): Money { return $this->inventory->getMoney(); } public function getReputation(): Reputation { return $this->reputation; } public function applyAttributeIncrease(string $attribute): void { if ($this->attributes->hasAvailablePoints()) { $this->attributes = $this->attributes->assignAvailablePoint($attribute); if ($attribute === 'constitution') { $this->hitPoints = $this->hitPoints->withIncrementedConstitution(); } } } public function addItemToInventory(Item $item): void { $this->inventory->add($item); } public function setLocationId(string $locationId): void { $this->locationId = $locationId; } public function isAlive(): bool { return $this->hitPoints->getCurrentHitPoints() > 0; } public function incrementWonBattles(): void { $this->statistics = $this->statistics->withIncreaseWonBattles(); } public function incrementLostBattles(): void { $this->statistics = $this->statistics->withIncreaseLostBattles(); } public function addXp(int $xp): void { $this->xp += $xp; } public function getBattlesWon(): int { return $this->statistics->getBattlesWon(); } public function getBattlesLost(): int { return $this->statistics->getBattlesLost(); } public function applyDamage($damageDone): void { $this->hitPoints = $this->hitPoints->withUpdatedCurrentValue(-$damageDone); } public function updateLevel(int $levelId): void { $points = $levelId - $this->levelId; $this->levelId = $levelId; $this->attributes = $this->attributes->addAvailablePoints($points); } public function setProfilePictureId(ImageId $profilePictureId): void { $this->profilePictureId = $profilePictureId; } /** * @return ImageId|null */ public function getProfilePictureId(): ?ImageId { return $this->profilePictureId; } public function removeProfilePicture(): void { $this->profilePictureId = null; } public function getInventory(): Inventory { return $this->inventory; } public function isMerchant(): bool { return $this->type->isMerchant(); } } ================================================ FILE: app/Modules/Character/Domain/CharacterId.php ================================================ value = $value; } public function getValue(): string { return $this->value; } public function isMerchant(): bool { return $this->value === self::MERCHANT; } } ================================================ FILE: app/Modules/Character/Domain/Gender.php ================================================ value = $value; } public function getValue(): string { return $this->value; } } ================================================ FILE: app/Modules/Character/Domain/HitPoints.php ================================================ getConstitution()); return new HitPoints($maximumHitPoints, $maximumHitPoints); } public function withIncrementedConstitution(): HitPoints { return new HitPoints( $this->currentHitPoints, $this->maximumHitPoints + self::constitutionToHitPoints(1) ); } public function withUpdatedCurrentValue(int $points): HitPoints { return new HitPoints( $this->currentHitPoints + $points, $this->maximumHitPoints ); } protected static function constitutionToHitPoints(int $constitutionPoints): int { return $constitutionPoints * 10 + self::throwTwoDices(); } public function __construct(int $currentHitPoints, int $maximumHitPoints) { $this->currentHitPoints = $currentHitPoints; $this->maximumHitPoints = $maximumHitPoints; } public function getCurrentHitPoints(): int { return $this->currentHitPoints; } public function getMaximumHitPoints(): int { return $this->maximumHitPoints; } } ================================================ FILE: app/Modules/Character/Domain/LocationId.php ================================================ value = $value; } public function getValue(): string { return $this->value; } public function equals(Name $otherName): bool { return $this->value === $otherName->value; } } ================================================ FILE: app/Modules/Character/Domain/Race.php ================================================ id = $id; $this->startingLocationId = $startingLocationId; $this->name = $name; $this->description = $description; $this->maleImage = $maleImage; $this->femaleImage = $femaleImage; $this->attributes = $attributes; } public function getId(): int { return $this->id; } public function getName(): string { return $this->name; } public function getImageByGender(string $gender):string { return $this->{"{$gender}_image"}; } public function getStartingLocationId(): string { return $this->startingLocationId; } public function getStrength(): int { return $this->attributes->getStrength(); } public function getAgility(): int { return $this->attributes->getAgility(); } public function getConstitution(): int { return $this->attributes->getConstitution(); } public function getIntelligence(): int { return $this->attributes->getIntelligence(); } public function getCharisma(): int { return $this->attributes->getCharisma(); } /** * @return string */ public function getDescription(): string { return $this->description; } /** * @return string */ public function getMaleImage(): string { return $this->maleImage; } /** * @return string */ public function getFemaleImage(): string { return $this->femaleImage; } } ================================================ FILE: app/Modules/Character/Domain/Reputation.php ================================================ value = $value; } public function getValue(): int { return $this->value; } } ================================================ FILE: app/Modules/Character/Domain/Statistics.php ================================================ statistics = new Collection($statistics); } public function withIncreaseWonBattles(): Statistics { $data = $this->statistics->all(); $data['battlesWon']++; return new self($data); } public function withIncreaseLostBattles(): Statistics { $data = $this->statistics->all(); $data['battlesLost']++; return new self($data); } public function getBattlesWon(): int { return (int)$this->statistics->get('battlesWon'); } public function getBattlesLost(): int { return (int)$this->statistics->get('battlesLost'); } } ================================================ FILE: app/Modules/Character/Infrastructure/ReconstitutionFactories/CharacterReconstitutionFactory.php ================================================ inventoryReconstitutionFactory = $inventoryReconstitutionFactory; } public function reconstitute(CharacterModel $characterModel): Character { $inventory = $this->inventoryReconstitutionFactory->reconstitute($characterModel->inventory); $profilePictureId = $characterModel->getProfilePictureId(); $character = new Character( CharacterId::fromString($characterModel->getId()), $characterModel->getRaceId(), $characterModel->getLevelNumber(), $characterModel->getLocationId(), $characterModel->getName(), new Gender($characterModel->getGender()), new CharacterType($characterModel->getType()), $characterModel->getXp(), new Reputation(0), new Attributes([ 'strength' => $characterModel->getStrength(), 'agility' => $characterModel->getAgility(), 'constitution' => $characterModel->getConstitution(), 'intelligence' => $characterModel->getIntelligence(), 'charisma' => $characterModel->getCharisma(), 'unassigned' => $characterModel->getAvailableAttributePoints(), ]), new HitPoints( $characterModel->getHitPoints(), $characterModel->getTotalHitPoints() ), new Statistics([ 'battlesLost' => $characterModel->getBattlesLost(), 'battlesWon' => $characterModel->getBattlesWon(), ]), $inventory, $characterModel->getUserId(), $profilePictureId ? ImageId::fromString($profilePictureId) : null ); return $character; } } ================================================ FILE: app/Modules/Character/Infrastructure/Repositories/CharacterRepository.php ================================================ characterReconstitutionFactory = $characterReconstitutionFactory; } /** * @return CharacterId * * @throws Exception */ public function nextIdentity(): CharacterId { return CharacterId::fromString($this->generateUuid()); } public function add(Character $character): void { $profilePictureId = $character->getProfilePictureId(); /** @var CharacterModel $characterModel */ CharacterModel::query()->create([ 'id' => $character->getId()->toString(), 'user_id' => $character->getUserId(), 'name' => $character->getName(), 'gender' => $character->getGender()->getValue(), 'type' => $character->getType()->getValue(), 'xp' => $character->getXp(), 'level_id' => $character->getLevelNumber(), 'reputation' => $character->getReputation()->getValue(), 'strength' => $character->getStrength(), 'agility' => $character->getAgility(), 'constitution' => $character->getConstitution(), 'intelligence' => $character->getIntelligence(), 'charisma' => $character->getCharisma(), 'hit_points' => $character->getHitPoints(), 'total_hit_points' => $character->getTotalHitPoints(), 'race_id' => $character->getRaceId(), 'location_id' => $character->getLocationId(), 'battles_won' => $character->getBattlesWon(), 'battles_lost' => $character->getBattlesLost(), 'profile_picture_id' => $profilePictureId ? $profilePictureId->toString() : null, ]); } public function getOne(CharacterId $characterId): Character { /** @var CharacterModel $characterModel */ $characterModel = CharacterModel::query()->with('inventory')->findOrFail($characterId->toString()); return $this->characterReconstitutionFactory->reconstitute($characterModel); } public function update(Character $character): void { /** @var CharacterModel $characterModel */ $characterModel = CharacterModel::query()->findOrFail($character->getId()->toString()); $profilePictureId = $character->getProfilePictureId(); $characterModel->update([ 'name' => $character->getName(), 'gender' => $character->getGender()->getValue(), 'xp' => $character->getXp(), 'level_id' => $character->getLevelNumber(), 'reputation' => $character->getReputation()->getValue(), 'strength' => $character->getStrength(), 'agility' => $character->getAgility(), 'constitution' => $character->getConstitution(), 'intelligence' => $character->getIntelligence(), 'charisma' => $character->getCharisma(), 'available_attribute_points' => $character->getUnassignedAttributePoints(), 'hit_points' => $character->getHitPoints(), 'total_hit_points' => $character->getTotalHitPoints(), 'battles_won' => $character->getBattlesWon(), 'battles_lost' => $character->getBattlesLost(), 'location_id' => $character->getLocationId(), 'profile_picture_id' => $profilePictureId ? $profilePictureId->toString() : null, ]); } } ================================================ FILE: app/Modules/Character/Infrastructure/Repositories/LocationRepository.php ================================================ generateUuid()); } } ================================================ FILE: app/Modules/Character/Infrastructure/Repositories/RaceRepository.php ================================================ findOrFail($raceId); return new Race( $race->getId(), $race->getStartingLocationId(), $race->getName(), $race->getDescription(), $race->getMaleImage(), $race->getFemaleImage(), new Attributes([ 'strength' => $race->getStrength(), 'agility' => $race->getAgility(), 'constitution' => $race->getConstitution(), 'intelligence' => $race->getIntelligence(), 'charisma' => $race->getCharisma(), ]) ); } } ================================================ FILE: app/Modules/Character/UI/Http/CommandMappers/AttackCharacterCommandMapper.php ================================================ user(); return new AttackCharacterCommand( CharacterId::fromString($userModel->character->getId()), CharacterId::fromString($defenderId) ); } } ================================================ FILE: app/Modules/Character/UI/Http/CommandMappers/CreateCharacterCommandMapper.php ================================================ user(); return new CreateCharacterCommand( $request->input('name'), $request->input('gender'), CharacterType::PLAYER, $request->input('race_id'), $userModel->getId() ); } } ================================================ FILE: app/Modules/Character/UI/Http/CommandMappers/IncreaseAttributeCommandMapper.php ================================================ input('attribute') ); } } ================================================ FILE: app/Modules/Character/UI/Http/CommandMappers/MoveCharacterCommandMapper.php ================================================ characterId = $characterId; $this->slot = $slot; $this->itemId = $itemId; } public function getCharacterId(): CharacterId { return $this->characterId; } public function getSlot(): int { return $this->slot; } public function getItemId(): ItemId { return $this->itemId; } } ================================================ FILE: app/Modules/Equipment/Application/Commands/CreateInventoryCommand.php ================================================ characterId = $characterId; } public function getCharacterId(): CharacterId { return $this->characterId; } } ================================================ FILE: app/Modules/Equipment/Application/Commands/CreateItemCommand.php ================================================ prototypeId = $prototypeId; $this->creatorCharacterId = $creatorCharacterId; } public function getPrototypeId(): ItemPrototypeId { return $this->prototypeId; } public function getCreatorCharacterId(): CharacterId { return $this->creatorCharacterId; } } ================================================ FILE: app/Modules/Equipment/Application/Commands/EquipItemCommand.php ================================================ itemId = $itemId; $this->ownerCharacterId = $ownerCharacterId; } public function getItemId(): ItemId { return $this->itemId; } public function getOwnerCharacterId(): CharacterId { return $this->ownerCharacterId; } } ================================================ FILE: app/Modules/Equipment/Application/Contracts/InventoryRepositoryInterface.php ================================================ getName(), $itemPrototype->getDescription(), $itemPrototype->getImageFilePath(), $itemPrototype->getType(), $itemPrototype->getEffects(), $itemPrototype->getPrice(), $itemPrototype->getId(), $creatorCharacterId ); } } ================================================ FILE: app/Modules/Equipment/Application/Services/InventoryService.php ================================================ inventoryRepository = $inventoryRepository; } public function create(CreateInventoryCommand $command):Inventory { $id = $this->inventoryRepository->nextIdentity(); $inventory = new Inventory($id, $command->getCharacterId(), Collection::make(), new Money(0)); $this->inventoryRepository->add($inventory); return $inventory; } public function equipItem(EquipItemCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getOwnerCharacterId()); $inventory->equip($command->getItemId()); $this->inventoryRepository->update($inventory); } public function unEquipItem(EquipItemCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getOwnerCharacterId()); $inventory->unEquipItem($command->getItemId()); $this->inventoryRepository->update($inventory); } } ================================================ FILE: app/Modules/Equipment/Application/Services/ItemService.php ================================================ characterRepository = $characterRepository; $this->itemRepository = $itemRepository; $this->itemPrototypeRepository = $itemPrototypeRepository; $this->itemFactory = $itemFactory; } public function create(CreateItemCommand $command): void { DB::transaction(function () use ($command) { $itemPrototype = $this->itemPrototypeRepository->getOne($command->getPrototypeId()); $character = $this->characterRepository->getOne($command->getCreatorCharacterId()); $itemId = $this->itemRepository->nextIdentity(); $item = $this->itemFactory->create($itemId, $itemPrototype, $character->getId()); $character->addItemToInventory($item); $this->itemRepository->add($item); $this->characterRepository->update($character); }); } } ================================================ FILE: app/Modules/Equipment/Domain/Inventory.php ================================================ count() > self::NUMBER_OF_SLOTS) { throw new NotEnoughSpaceInContainerException( "Not enough space in the Inventory for {$items->count()} new items" ); } $items->each(static function ($item) { if (!($item instanceof InventoryItem)) { throw new InvalidArgumentException('Trying to populate inventory with non inventory item'); } }); $this->id = $id; $this->characterId = $characterId; $this->items = $items; $this->money = $money; } public function getId(): InventoryId { return $this->id; } public function add(Item $item): void { $item = new InventoryItem($item, ItemStatus::inBackpack()); $slot = $this->findFreeSlot(); $this->items->put($slot, $item); } public function getEquippedItemsEffect(string $itemEffectType): int { return (int)$this->getEquippedItems()->reduce(static function ($carry, InventoryItem $item) use ($itemEffectType) { $itemEffect = $item->getItemEffect($itemEffectType); return $carry + $itemEffect; }); } public function unEquipItem(ItemId $itemId): void { $equippedItem = $this->findEquippedItem($itemId); if ($equippedItem) { $equippedItem->unEquip(); } } public function equip(ItemId $itemId): void { $item = $this->findItem($itemId); if (!$item) { throw new ItemNotInContainer('Cannot equip item that is not in the inventory'); } if ($equippedItem = $this->findEquippedItemOfType($item->getType())) { $equippedItem->unEquip(); } $item->equip(); } public function getCharacterId(): CharacterId { return $this->characterId; } public function getItems(): Collection { return $this->items; } public function takeOut(ItemId $itemId): Item { $slot = $this->items->search(static function (InventoryItem $item) use ($itemId) { return $item->getId()->equals($itemId); }); if ($slot === false) { throw new ItemNotInContainer('Cannot take out item from empty slot'); } /** @var InventoryItem $item */ $item = $this->items->get($slot); $this->items->forget($slot); return $item->toBaseItem(); } public function putMoneyIn(Money $money): void { $this->money = $this->money->combine($money); } public function takeMoneyOut(Money $money): Money { $this->money = $this->money->remove($money); return $money; } public function getMoney(): Money { return $this->money; } public function findItem(ItemId $itemId):? InventoryItem { return $this->items->first(static function (InventoryItem $item) use ($itemId) { return $item->getId()->equals($itemId); }); } private function findFreeSlot(): int { for ($slot = 0; $slot < self::NUMBER_OF_SLOTS; $slot++) { if (!$this->items->has($slot)) { return $slot; } } throw new ContainerIsFullException('Cannot add to full inventory'); } private function findEquippedItem(ItemId $itemId):? InventoryItem { return $this->items->first(static function (InventoryItem $item) use ($itemId) { return $item->getId()->equals($itemId) && $item->isEquipped(); }); } private function findEquippedItemOfType(ItemType $type):? InventoryItem { return $this->items->first(static function (InventoryItem $item) use ($type) { return $item->isOfType($type) && $item->isEquipped(); }); } private function getEquippedItems(): Collection { return $this->items->filter(static function (InventoryItem $item) { return $item->isEquipped(); }); } } ================================================ FILE: app/Modules/Equipment/Domain/InventoryId.php ================================================ getId(), $item->getName(), $item->getDescription(), $item->getImageFilePath(), $item->getType(), $item->getEffects(), $item->getPrice(), $item->getPrototypeId(), $item->getCreatorCharacterId() ); $this->status = $status; } public function getStatus(): ItemStatus { return $this->status; } public function isEquipped(): bool { return $this->status->equals(ItemStatus::equipped()); } public function equip(): void { $this->status = ItemStatus::equipped(); } public function unEquip(): void { $this->status = ItemStatus::inBackpack(); } } ================================================ FILE: app/Modules/Equipment/Domain/InventorySlot.php ================================================ slot = $slot; } public function getSlot(): int { return $this->slot; } } ================================================ FILE: app/Modules/Equipment/Domain/Item.php ================================================ id = $id; $this->name = $name; $this->description = $description; $this->imageFilePath = $imageFilePath; $this->type = $type; $this->effects = $effects; $this->price = $price; $this->prototypeId = $prototypeId; $this->creatorCharacterId = $creatorCharacterId; } public function getId(): ItemId { return $this->id; } public function getName(): string { return $this->name; } public function getDescription(): string { return $this->description; } public function getImageFilePath(): string { return $this->imageFilePath; } public function getType(): ItemType { return $this->type; } public function getEffects(): Collection { return $this->effects; } public function getPrototypeId(): ItemPrototypeId { return $this->prototypeId; } public function getCreatorCharacterId(): CharacterId { return $this->creatorCharacterId; } public function getItemEffect(string $itemEffectType): int { return (int)$this->getEffectsOfType($itemEffectType) ->reduce(static function ($carry, ItemEffect $itemEffect) { return $carry + $itemEffect->getQuantity(); }); } private function getEffectsOfType(string $itemEffectType): Collection { return $this->effects->filter(static function (ItemEffect $effect) use ($itemEffectType) { return $effect->getType() === $itemEffectType; }); } public function equals(Item $otherItem): bool { return $this->getId()->equals($otherItem->getId()); } public function isOfType(ItemType $type): bool { return $this->getType()->equals($type); } public function getPrice(): ItemPrice { return $this->price; } public function changePrice(ItemPrice $price): self { $this->price = $price; return $this; } public function toBaseItem(): Item { return new Item( $this->getId(), $this->getName(), $this->getDescription(), $this->getImageFilePath(), $this->getType(), $this->getEffects(), $this->getPrice(), $this->getPrototypeId(), $this->getCreatorCharacterId() ); } } ================================================ FILE: app/Modules/Equipment/Domain/ItemEffect.php ================================================ quantity = $quantity; $this->type = $type; } public static function ofType(int $quantity, string $type): ItemEffect { if (!in_array($type, self::TYPES, true)) { throw new InvalidArgumentException("$type is not a valid Item Effect type"); } return new self($quantity, $type); } public static function damage(int $quantity): ItemEffect { return new self($quantity, self::DAMAGE); } public static function armor(int $quantity): ItemEffect { return new self($quantity, self::ARMOR); } public function getQuantity(): int { return $this->quantity; } public function getType(): string { return $this->type; } } ================================================ FILE: app/Modules/Equipment/Domain/ItemId.php ================================================ amount = $amount; } public static function ofAmount(int $amount): self { return new self($amount); } public function getAmount(): int { return $this->amount; } } ================================================ FILE: app/Modules/Equipment/Domain/ItemPrototype.php ================================================ id = $id; $this->name = $name; $this->description = $description; $this->imageFilePath = $imageFilePath; $this->type = $type; $this->effects = $effects; $this->price = $price; } public function getId(): ItemPrototypeId { return $this->id; } public function getName(): string { return $this->name; } public function getDescription(): string { return $this->description; } public function getImageFilePath(): string { return $this->imageFilePath; } public function getType(): ItemType { return $this->type; } public function getEffects(): Collection { return $this->effects; } public function getPrice(): ItemPrice { return $this->price; } } ================================================ FILE: app/Modules/Equipment/Domain/ItemPrototypeId.php ================================================ type = $type; } public static function ofStatus(string $status): self { if (!in_array($status, self::STATUSES, true)) { throw new InvalidArgumentException("$status is not a valid Item Status"); } return new self($status); } public static function inBackpack(): self { return new self(self::IN_BACKPACK); } public static function equipped(): self { return new self(self::EQUIPPED); } public function toString(): string { return $this->type; } public function equals(self $type): bool { return $this->type === $type->toString(); } } ================================================ FILE: app/Modules/Equipment/Domain/ItemType.php ================================================ type = $type; } public static function ofType(string $type): ItemType { if (!in_array($type, self::TYPES, true)) { throw new InvalidArgumentException("$type is not a valid Item Type"); } return new self($type); } public static function headGear(): ItemType { return new self(self::HEAD_GEAR); } public static function bodyArmor(): ItemType { return new self(self::BODY_ARMOR); } public static function mainHand(): ItemType { return new self(self::MAIN_HAND); } public static function offHand(): ItemType { return new self(self::OFF_HAND); } public function toString(): string { return $this->type; } public function equals(ItemType $type): bool { return $this->type === $type->toString(); } } ================================================ FILE: app/Modules/Equipment/Domain/Money.php ================================================ value = $value; } public function getValue(): int { return $this->value; } public function remove(Money $money): self { if ($this->value < $money->getValue()) { throw new NotEnoughMoneyToRemove('Cannot remove more money than there is'); } return new self($this->value - $money->getValue()); } public function combine(Money $money): self { return new self($this->value + $money->getValue()); } } ================================================ FILE: app/Modules/Equipment/Infrastructure/ReconstitutionFactories/InventoryItemReconstitutionFactory.php ================================================ itemReconstitutionFactory = $itemReconstitutionFactory; } public function reconstitute(ItemModel $model): InventoryItem { return new InventoryItem( $this->itemReconstitutionFactory->reconstitute($model), ItemStatus::ofStatus($model->pivot->status) ); } } ================================================ FILE: app/Modules/Equipment/Infrastructure/ReconstitutionFactories/InventoryReconstitutionFactory.php ================================================ inventoryItemReconstitutionFactory = $inventoryItemReconstitutionFactory; } public function reconstitute(InventoryModel $inventoryModel): Inventory { $items = $inventoryModel->items->mapWithKeys(function (ItemModel $itemModel) { $key = $itemModel->getInventorySlotNumber(); $inventoryItem = $this->inventoryItemReconstitutionFactory->reconstitute($itemModel); return [$key => $inventoryItem]; }); return new Inventory( InventoryId::fromString($inventoryModel->getId()), CharacterId::fromString($inventoryModel->getCharacterId()), $items, new Money($inventoryModel->getMoney()) ); } } ================================================ FILE: app/Modules/Equipment/Infrastructure/ReconstitutionFactories/ItemPrototypeReconstitutionFactory.php ================================================ getEffects())->map(static function (array $effect) { return ItemEffect::ofType( $effect['quantity'], $effect['type']); }); $itemPrototype = new ItemPrototype( ItemPrototypeId::fromString($model->getId()), $model->getName(), $model->getDescription(), $model->getImageFilePath(), ItemType::ofType($model->getType()), $effects, ItemPrice::ofAmount($model->getPrice()) ); return $itemPrototype; } } ================================================ FILE: app/Modules/Equipment/Infrastructure/ReconstitutionFactories/ItemReconstitutionFactory.php ================================================ getEffects())->map(static function (array $effect) { return ItemEffect::ofType( $effect['quantity'], $effect['type'] ); }); $item = new Item( ItemId::fromString($model->getId()), $model->getName(), $model->getDescription(), $model->getImageFilePath(), ItemType::ofType($model->getType()), $effects, ItemPrice::ofAmount($model->getPrice()), ItemPrototypeId::fromString($model->getPrototypeId()), CharacterId::fromString($model->getCreatorCharacterId()) ); return $item; } } ================================================ FILE: app/Modules/Equipment/Infrastructure/Repositories/InventoryRepository.php ================================================ reconstitutionFactory = $reconstitutionFactory; } /** * @return InventoryId * * @throws Exception */ public function nextIdentity(): InventoryId { return InventoryId::fromString($this->generateUuid()); } public function add(Inventory $inventory): void { InventoryModel::query()->create([ 'id' => $inventory->getId()->toString(), 'character_id' => $inventory->getCharacterId()->toString(), 'money' => $inventory->getMoney()->getValue(), ]); } public function forCharacter(CharacterId $characterId): Inventory { /** @var InventoryModel $model */ $model = InventoryModel::query()->where('character_id', $characterId->toString())->firstOrFail(); return $this->reconstitutionFactory->reconstitute($model); } public function update(Inventory $inventory): void { /** @var InventoryModel $inventoryModel */ $inventoryModel = InventoryModel::query()->findOrFail($inventory->getId()->toString()); $inventoryItems = $inventory->getItems()->mapWithKeys(static function (InventoryItem $item, int $slot) { $itemId = $item->getId()->toString(); return [ $itemId => [ 'item_id' => $itemId, 'status' => $item->getStatus()->toString(), 'inventory_slot_number' => $slot, ], ]; }); $inventoryModel->items()->sync($inventoryItems->all()); $inventoryModel->update([ 'money' => $inventory->getMoney()->getValue(), ]); } } ================================================ FILE: app/Modules/Equipment/Infrastructure/Repositories/ItemPrototypeRepository.php ================================================ reconstitutionFactory = $reconstitutionFactory; } /** * @return ItemPrototypeId * * @throws Exception */ public function nextIdentity(): ItemPrototypeId { return ItemPrototypeId::fromString($this->generateUuid()); } public function getOne(ItemPrototypeId $itemPrototypeId): ItemPrototype { /** @var \App\Models\ItemPrototypeModel $model */ $model = ItemPrototypeModel::query()->findOrFail($itemPrototypeId->toString()); return $this->reconstitutionFactory->reconstitute($model); } } ================================================ FILE: app/Modules/Equipment/Infrastructure/Repositories/ItemRepository.php ================================================ reconstitutionFactory = $reconstitutionFactory; } /** * @return ItemId * * @throws Exception */ public function nextIdentity(): ItemId { return ItemId::fromString($this->generateUuid()); } public function add(Item $item): void { $effects = $item->getEffects()->map(static function (ItemEffect $effect) { return [ 'quantity' => $effect->getQuantity(), 'type' => $effect->getType(), ]; })->toJson(); ItemModel::query()->create([ 'id' => $item->getId()->toString(), 'prototype_id' => $item->getPrototypeId()->toString(), 'creator_character_id' => $item->getCreatorCharacterId()->toString(), 'name' => $item->getName(), 'description' => $item->getDescription(), 'effects' => $effects, 'price' => $item->getPrice()->getAmount(), 'image_file_path' => $item->getImageFilePath(), 'type' => $item->getType()->toString(), ]); } public function getOne(ItemId $itemId): Item { /** @var ItemModel $model */ $model = ItemModel::query()->findOrFail($itemId->toString()); return $this->reconstitutionFactory->reconstitute($model); } public function update(Item $item): void { $effects = $item->getEffects()->map(static function (ItemEffect $effect) { return [ 'quantity' => $effect->getQuantity(), 'type' => $effect->getType(), ]; })->toJson(); ItemModel::query()->where('id', $item->getId()->toString())->update([ 'prototype_id' => $item->getPrototypeId()->toString(), 'creator_character_id' => $item->getCreatorCharacterId()->toString(), 'name' => $item->getName(), 'description' => $item->getDescription(), 'effects' => $effects, 'price' => $item->getPrice()->getAmount(), 'image_file_path' => $item->getImageFilePath(), 'type' => $item->getType()->toString(), ]); } } ================================================ FILE: app/Modules/Equipment/UI/Http/CommandMappers/AddItemToInventoryCommandMapper.php ================================================ user(); return new AddItemToInventoryCommand( CharacterId::fromString($userModel->character->getId()), (int)$request->input('inventory_slot'), ItemId::fromString((string)$request->input('item_id')) ); } } ================================================ FILE: app/Modules/Equipment/UI/Http/CommandMappers/CreateItemCommandMapper.php ================================================ user(); return new CreateItemCommand( ItemPrototypeId::fromString($request->input('prototype_item_id')), CharacterId::fromString($userModel->character->getId()) ); } } ================================================ FILE: app/Modules/Equipment/UI/Http/CommandMappers/EquipItemCommandMapper.php ================================================ user(); return new EquipItemCommand( ItemId::fromString((string)$request->route('item')), CharacterId::fromString($userModel->character->getId()) ); } } ================================================ FILE: app/Modules/Generic/Domain/BaseId.php ================================================ id = $id; } /** * @param string $id * * @return static */ public static function fromString(string $id) { return new static($id); } public function toString(): string { return $this->id; } public function equals(BaseId $otherId): bool { return $this->toString() === $otherId->toString(); } } ================================================ FILE: app/Modules/Generic/Domain/Container/ContainerIsFullException.php ================================================ type = $type; } public function getType(): string { return $this->type; } public function isStore(): bool { return $this->type === self::STORE; } } ================================================ FILE: app/Modules/Image/Application/Commands/AddImageCommand.php ================================================ characterId = $characterId; $this->uploadedFile = $uploadedFile; } public function getCharacterId(): CharacterId { return $this->characterId; } public function getUploadedFile(): UploadedFile { return $this->uploadedFile; } } ================================================ FILE: app/Modules/Image/Application/Contracts/ImageRepositoryInterface.php ================================================ toString() . '.' . $extension; return new Image( $imageId, $characterId, ImageFile::full('full_' . $fileName), ImageFile::small('small_' . $fileName), ImageFile::icon('icon_' . $fileName) ); } } ================================================ FILE: app/Modules/Image/Application/Services/ProfilePictureService.php ================================================ imageFactory = $imageFactory; $this->imageRepository = $imageRepository; $this->characterService = $characterService; } public function update(AddImageCommand $command): void { $imageId = $this->imageRepository->nextIdentity(); $profilePicture = $this->imageFactory->create( $imageId, $command->getCharacterId(), $command->getUploadedFile()->getClientOriginalExtension() ); $this->imageRepository->delete($command->getCharacterId()); $this->imageRepository->add($profilePicture, $command->getUploadedFile()); $this->characterService->updateProfilePicture($profilePicture); } public function delete(CharacterId $characterId): void { $this->imageRepository->delete($characterId); $this->characterService->removeProfilePicture($characterId); } } ================================================ FILE: app/Modules/Image/Domain/Image.php ================================================ id = $id; $this->characterId = $characterId; $this->fullSizeFile = $fullSizeFile; $this->smallSizeFile = $smallSizeFile; $this->iconSizeFile = $iconSizeFile; } public function getId(): ImageId { return $this->id; } public function getCharacterId(): CharacterId { return $this->characterId; } public function getFullSizeFile(): ImageFile { return $this->fullSizeFile; } public function getSmallSizeFile(): ImageFile { return $this->smallSizeFile; } public function getIconSizeFile(): ImageFile { return $this->iconSizeFile; } } ================================================ FILE: app/Modules/Image/Domain/ImageFile.php ================================================ fileName = $fileName; $this->width = $width; } public static function full(string $fileName): self { return new self($fileName, self::IMAGE_WIDTH_FULL); } public static function small(string $fileName): self { return new self($fileName, self::IMAGE_WIDTH_SMALL); } public static function icon(string $fileName): self { return new self($fileName, self::IMAGE_WIDTH_ICON); } public function getFileName(): string { return $this->fileName; } public function getWidth(): int { return $this->width; } } ================================================ FILE: app/Modules/Image/Domain/ImageId.php ================================================ filesystem = $filesystem; $this->imageManager = $imageManager; } /** * @return ImageId * * @throws Exception */ public function nextIdentity(): ImageId { return ImageId::fromString($this->generateUuid()); } public function add(Image $image, UploadedFile $uploadedFile): void { $urlPath = $this->getUrlPath($image->getCharacterId()); $this->writeFiles($image, $uploadedFile); ImageModel::query()->create([ 'id' => $image->getId()->toString(), 'character_id' => $image->getCharacterId()->toString(), 'file_path_full' => $urlPath . $image->getFullSizeFile()->getFileName(), 'file_path_small' => $urlPath . $image->getSmallSizeFile()->getFileName(), 'file_path_icon' => $urlPath . $image->getIconSizeFile()->getFileName(), ]); } public function delete(CharacterId $characterId): void { $this->filesystem->deleteDirectory($this->getFolderPath($characterId)); ImageModel::query()->where('character_id', '=', $characterId->toString())->delete(); } private function writeFiles(Image $image, UploadedFile $uploadedFile): void { $folderPath = $this->getFolderPath($image->getCharacterId()); $this->createFolderIfMissing($folderPath); $imageFile = $this->imageManager->make($uploadedFile); $this->writeFile($image->getFullSizeFile(), $folderPath, $imageFile); $this->writeFile($image->getSmallSizeFile(), $folderPath, $imageFile); $this->writeFile($image->getIconSizeFile(), $folderPath, $imageFile); } private function writeFile( ImageFile $imageFile, string $folderPath, ImageManagerFile $imageManagerFile ): string { $filePath = $folderPath . $imageFile->getFileName(); $imageManagerFile ->resize($imageFile->getWidth(), null, static function (Constraint $constraint) { $constraint->aspectRatio(); }) ->save($filePath); return $filePath; } private function createFolderIfMissing(string $fullFolderPath): bool { return $this->filesystem->exists($fullFolderPath) or $this->filesystem->makeDirectory($fullFolderPath, 0755, true); } private function getFolderPath(CharacterId $characterId): string { return storage_path( 'app' . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . $this->getCharacterImageFolder($characterId) ); } private function getUrlPath(CharacterId $characterId): string { return 'storage' . DIRECTORY_SEPARATOR . $this->getCharacterImageFolder($characterId); } private function getCharacterImageFolder(CharacterId $characterId): string { return 'images' . DIRECTORY_SEPARATOR . 'characters' . DIRECTORY_SEPARATOR . $characterId->toString() . DIRECTORY_SEPARATOR; } } ================================================ FILE: app/Modules/Image/UI/Http/CommandMappers/AddImageCommandMapper.php ================================================ getLevelThreshold($levelId) < $xp) { $levelId++; } return $this->getLevel($levelId - 1); } public function getLevel(int $levelId): Level { return new Level($levelId, $this->getLevelThreshold($levelId), $this->getLevelThreshold($levelId + 1)); } public function getLevels(int $limit = 100): array { $levelId = 1; $levels = []; while ($levelId <= $limit) { $levels[] = new Level( $levelId, $this->getLevelThreshold($levelId), $this->getLevelThreshold($levelId + 1) ); $levelId++; } return $levels; } private function getLevelThreshold(int $levelId): float { $rawValue = 4 * ($levelId ** 3) / 5; return floor($rawValue); } } ================================================ FILE: app/Modules/Level/Domain/Level.php ================================================ id = $id; $this->currentLevelThreshold = $currentLevelThreshHold; $this->nextLevelThreshold = $nextLevelXpThreshold; } public function getId(): int { return $this->id; } public function getCurrentXpThreshold(): int { return $this->currentLevelThreshold; } public function getNextXpThreshold(): int { return $this->nextLevelThreshold; } public function getProgress(int $xp):float { $progressRange = $this->nextLevelThreshold - $this->currentLevelThreshold; $progressMade = $xp - $this->currentLevelThreshold; $progressMade = $progressMade < 0 ? 0 : $progressMade; return ($progressMade / $progressRange) * 100; } } ================================================ FILE: app/Modules/Message/Application/Commands/SendMessageCommand.php ================================================ senderId = $senderId; $this->recipientId = $recipientId; $this->content = $content; } public function getSenderId(): CharacterId { return $this->senderId; } public function getRecipientId(): CharacterId { return $this->recipientId; } public function getContent(): string { return $this->content; } } ================================================ FILE: app/Modules/Message/Application/Contracts/MessageRepositoryInterface.php ================================================ messageRepository = $messageRepository; } public function send(SendMessageCommand $command): void { $message = new Message( $this->messageRepository->nextIdentity(), $command->getSenderId(), $command->getRecipientId(), $command->getContent() ); $this->messageRepository->add($message); } } ================================================ FILE: app/Modules/Message/Domain/Message.php ================================================ id = $id; $this->senderId = $senderId; $this->recipientId = $recipientId; $this->content = $content; $this->state = $state; } public function getId(): MessageId { return $this->id; } public function getSenderId(): CharacterId { return $this->senderId; } public function getRecipientId(): CharacterId { return $this->recipientId; } public function getContent(): string { return $this->content; } public function getState(): string { return $this->state; } } ================================================ FILE: app/Modules/Message/Domain/MessageId.php ================================================ generateUuid()); } public function add(Message $message): void { MessageModel::query()->create([ 'id' => $message->getId()->toString(), 'from_id' => $message->getSenderId()->toString(), 'to_id' => $message->getRecipientId()->toString(), 'content' => $message->getContent(), 'state' => $message->getState() ]); } } ================================================ FILE: app/Modules/Message/UI/Http/CommandMappers/SendMessageCommandMapper.php ================================================ user(); $currentCharacter = $user->character; return new SendMessageCommand( CharacterId::fromString($currentCharacter->id), CharacterId::fromString((string)$request->route('character')), (string)$request->get('content') ); } } ================================================ FILE: app/Modules/Trade/Application/Commands/AddItemToStoreCommand.php ================================================ characterId = $characterId; $this->slot = $slot; $this->itemId = $itemId; } public function getCharacterId(): CharacterId { return $this->characterId; } public function getSlot(): int { return $this->slot; } public function getItemId(): ItemId { return $this->itemId; } } ================================================ FILE: app/Modules/Trade/Application/Commands/BuyItemCommand.php ================================================ customerId = $customerId; $this->storeId = $storeId; $this->itemId = $itemId; } public function getCustomerId(): CharacterId { return $this->customerId; } public function getStoreId(): StoreId { return $this->storeId; } public function getItemId(): ItemId { return $this->itemId; } } ================================================ FILE: app/Modules/Trade/Application/Commands/ChangeItemPriceCommand.php ================================================ itemId = $itemId; $this->itemPrice = $itemPrice; $this->characterId = $characterId; $this->containerType = $containerType; } public function getItemId(): ItemId { return $this->itemId; } public function getItemPrice(): ItemPrice { return $this->itemPrice; } public function getCharacterId(): CharacterId { return $this->characterId; } public function getContainerType(): ContainerType { return $this->containerType; } } ================================================ FILE: app/Modules/Trade/Application/Commands/CreateStoreCommand.php ================================================ characterId = $characterId; } public function getCharacterId(): CharacterId { return $this->characterId; } } ================================================ FILE: app/Modules/Trade/Application/Commands/MoveItemToContainerCommand.php ================================================ itemId = $itemId; $this->characterId = $characterId; } public function getItemId(): ItemId { return $this->itemId; } public function getCharacterId(): CharacterId { return $this->characterId; } } ================================================ FILE: app/Modules/Trade/Application/Commands/MoveMoneyToContainerCommand.php ================================================ money = $money; $this->characterId = $characterId; } public function getMoney(): Money { return $this->money; } public function getCharacterId(): CharacterId { return $this->characterId; } } ================================================ FILE: app/Modules/Trade/Application/Commands/SellItemCommand.php ================================================ customerId = $customerId; $this->storeId = $storeId; $this->itemId = $itemId; } public function getCustomerId(): CharacterId { return $this->customerId; } public function getStoreId(): StoreId { return $this->storeId; } public function getItemId(): ItemId { return $this->itemId; } } ================================================ FILE: app/Modules/Trade/Application/Contracts/StoreRepositoryInterface.php ================================================ storeRepository = $storeRepository; } public function create(CreateStoreCommand $command): Store { $id = $this->storeRepository->nextIdentity(); $store = new Store($id, $command->getCharacterId(), StoreType::sellOnly(), Collection::make(), new Money(0)); $this->storeRepository->add($store); return $store; } } ================================================ FILE: app/Modules/Trade/Application/Services/ManageStoreService.php ================================================ storeRepository = $storeRepository; $this->inventoryRepository = $inventoryRepository; $this->itemRepository = $itemRepository; } public function moveItemToStore(MoveItemToContainerCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getCharacterId()); $store = $this->storeRepository->forCharacter($command->getCharacterId()); $item = $inventory->takeOut($command->getItemId()); $store->add($item); $this->inventoryRepository->update($inventory); $this->storeRepository->update($store); } public function moveItemToInventory(MoveItemToContainerCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getCharacterId()); $store = $this->storeRepository->forCharacter($command->getCharacterId()); $item = $store->takeOut($command->getItemId()); $inventory->add($item); $this->inventoryRepository->update($inventory); $this->storeRepository->update($store); } public function changeItemPrice(ChangeItemPriceCommand $command): void { $container = $command->getContainerType()->isStore() ? $this->storeRepository->forCharacter($command->getCharacterId()) : $this->inventoryRepository->forCharacter($command->getCharacterId()); $item = $container->findItem($command->getItemId()); if ($item === null) { throw new ItemNotInContainer('Cannot find item'); } $item->changePrice($command->getItemPrice()); $this->itemRepository->update($item); } public function moveMoneyToStore(MoveMoneyToContainerCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getCharacterId()); $store = $this->storeRepository->forCharacter($command->getCharacterId()); $money = $inventory->takeMoneyOut($command->getMoney()); $store->putMoneyIn($money); $this->inventoryRepository->update($inventory); $this->storeRepository->update($store); } public function moveMoneyToInventory(MoveMoneyToContainerCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getCharacterId()); $store = $this->storeRepository->forCharacter($command->getCharacterId()); $money = $store->takeMoneyOut($command->getMoney()); $inventory->putMoneyIn($money); $this->inventoryRepository->update($inventory); $this->storeRepository->update($store); } } ================================================ FILE: app/Modules/Trade/Application/Services/TradeService.php ================================================ storeRepository = $storeRepository; $this->inventoryRepository = $inventoryRepository; $this->itemPrototypeRepository = $itemPrototypeRepository; $this->itemRepository = $itemRepository; } public function buyItem(BuyItemCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getCustomerId()); $store = $this->storeRepository->getOne($command->getStoreId()); $item = $store->takeOut($command->getItemId()); $money = $inventory->takeMoneyOut(new Money($item->getPrice()->getAmount())); $inventory->add($item); $store->putMoneyIn($money); $this->inventoryRepository->update($inventory); $this->storeRepository->update($store); } public function sellItem(SellItemCommand $command): void { $inventory = $this->inventoryRepository->forCharacter($command->getCustomerId()); $store = $this->storeRepository->getOne($command->getStoreId()); $item = $inventory->takeOut($command->getItemId()); $itemPrototype = $this->itemPrototypeRepository->getOne($item->getPrototypeId()); $prototypePrice = $itemPrototype->getPrice()->getAmount(); $traderBuyPrice = (int)floor($prototypePrice * 0.75); $customerSellPrice = $item->getPrice()->getAmount(); if ($traderBuyPrice < $customerSellPrice) { throw new SellPriceIsTooHigh( "The store is willing to pay no more than {$traderBuyPrice} coins for {$itemPrototype->getName()}" ); } $money = $store->takeMoneyOut(new Money($customerSellPrice)); $item->changePrice(ItemPrice::ofAmount($prototypePrice)); $store->add($item); $inventory->putMoneyIn($money); $this->itemRepository->update($item); $this->inventoryRepository->update($inventory); $this->storeRepository->update($store); } } ================================================ FILE: app/Modules/Trade/Domain/Exception/SellPriceIsTooHigh.php ================================================ count() >= self::NUMBER_OF_SLOTS) { throw new NotEnoughSpaceInContainerException( "Not enough space in the Store for {$items->count()} new items" ); } $this->id = $id; $this->characterId = $characterId; $this->type = $type; $this->items = $items; $this->money = $money; } public function getId(): StoreId { return $this->id; } public function addItemToSlot(int $slot, Item $item): void { if ($slot >= self::NUMBER_OF_SLOTS) { throw new ContainerSlotOutOfRangeException("Store slot $slot is out of range."); } if ($this->items->has($slot)) { throw new ContainerSlotIsTakenException("Store slot $slot is already take"); } $this->items->put($slot, $item); } public function add(Item $item): void { $slot = $this->findFreeSlot(); $this->items->put($slot, $item); } private function findFreeSlot(): int { for ($slot = 0; $slot < self::NUMBER_OF_SLOTS; $slot++) { if (!$this->items->has($slot)) { return $slot; } } throw new ContainerIsFullException('Cannot add to full store'); } public function findItem(ItemId $itemId): ?Item { return $this->items->first(static function (Item $item) use ($itemId) { return $item->getId()->equals($itemId); }); } public function findItemsOfType(ItemType $type): Collection { return $this->items->filter(static function (Item $item) use ($type) { return $item->isOfType($type); }); } public function getCharacterId(): CharacterId { return $this->characterId; } public function getItems(): Collection { return $this->items; } public function takeOut(ItemId $itemId): Item { $slot = $this->items->search(static function (Item $item) use ($itemId) { return $item->getId()->equals($itemId); }); if ($slot === false) { throw new ItemNotInContainer('Cannot take out item from empty slot'); } /** @var Item $item */ $item = $this->items->get($slot); $this->items->forget($slot); return $item->toBaseItem(); } public function putMoneyIn(Money $money): void { $this->money = $this->money->combine($money); } public function takeMoneyOut(Money $money): Money { if ($this->type->isSellOnly()) { throw new StoreDoesNotBuyItems('Store does not buy items'); } $this->money = $this->money->remove($money); return $money; } public function getMoney(): Money { return $this->money; } } ================================================ FILE: app/Modules/Trade/Domain/StoreId.php ================================================ type = $type; } public static function ofType(string $type): self { if (!in_array($type, self::TYPES, true)) { throw new InvalidArgumentException("$type is not a valid Store Type"); } return new self($type); } public static function buyAndSell(): self { return new self(self::BUY_AND_SELL); } public static function sellOnly(): self { return new self(self::SELL_ONLY); } public function toString(): string { return $this->type; } public function isSellOnly(): bool { return $this->type === self::SELL_ONLY; } } ================================================ FILE: app/Modules/Trade/Infrastructure/ReconstitutionFactories/StoreReconstitutionFactory.php ================================================ itemReconstitutionFactory = $itemReconstitutionFactory; } public function reconstitute(StoreModel $storeModel): Store { $items = $storeModel->items->mapWithKeys(function (ItemModel $itemModel) { $key = $itemModel->getInventorySlotNumber(); $inventoryItem = $this->itemReconstitutionFactory->reconstitute($itemModel); return [$key => $inventoryItem]; }); return new Store( StoreId::fromString($storeModel->getId()), CharacterId::fromString($storeModel->getCharacterId()), StoreType::ofType($storeModel->getType()), $items, new Money($storeModel->getMoney()) ); } } ================================================ FILE: app/Modules/Trade/Infrastructure/Repositories/StoreRepository.php ================================================ reconstitutionFactory = $reconstitutionFactory; } /** * @return StoreId * * @throws Exception */ public function nextIdentity(): StoreId { return StoreId::fromString($this->generateUuid()); } public function add(Store $store): void { StoreModel::query()->create([ 'id' => $store->getId()->toString(), 'character_id' => $store->getCharacterId()->toString(), 'money' => $store->getMoney()->getValue(), ]); } public function getOne(StoreId $storeId): Store { /** @var StoreModel $model */ $model = StoreModel::query()->findOrFail($storeId->toString()); return $this->reconstitutionFactory->reconstitute($model); } public function forCharacter(CharacterId $characterId): Store { /** @var StoreModel $model */ $model = StoreModel::query()->where('character_id', $characterId->toString())->firstOrFail(); return $this->reconstitutionFactory->reconstitute($model); } public function update(Store $store): void { /** @var StoreModel $storeModel */ $storeModel = StoreModel::query()->findOrFail($store->getId()->toString()); $storeItems = $store->getItems()->mapWithKeys(static function (Item $item, int $slot) { $itemId = $item->getId()->toString(); return [ $itemId => [ 'item_id' => $itemId, 'price' => $item->getPrice()->getAmount(), 'inventory_slot_number' => $slot, ], ]; }); $storeModel->items()->sync($storeItems->all()); $storeModel->update([ 'money' => $store->getMoney()->getValue(), ]); } } ================================================ FILE: app/Modules/Trade/UI/Http/CommandMappers/BuyItemCommandMapper.php ================================================ user(); return new BuyItemCommand( CharacterId::fromString($userModel->character->getId()), StoreId::fromString((string)$request->route('store')), ItemId::fromString((string)$request->route('item')) ); } } ================================================ FILE: app/Modules/Trade/UI/Http/CommandMappers/ChangeItemPriceCommandMapper.php ================================================ user(); return new ChangeItemPriceCommand( ItemId::fromString((string)$request->route('item')), ItemPrice::ofAmount((int)$request->post('price')), CharacterId::fromString($userModel->character->getId()), ContainerType::fromString((string)$request->post('containerType')) ); } } ================================================ FILE: app/Modules/Trade/UI/Http/CommandMappers/MoveItemToContainerCommandMapper.php ================================================ user(); return new MoveItemToContainerCommand( ItemId::fromString((string)$request->route('item')), CharacterId::fromString($userModel->character->getId()) ); } } ================================================ FILE: app/Modules/Trade/UI/Http/CommandMappers/MoveMoneyToContainerCommandMapper.php ================================================ user(); return new MoveMoneyToContainerCommand( new Money((int)$request->post('money_amount', 0)), CharacterId::fromString($userModel->character->getId()) ); } } ================================================ FILE: app/Modules/Trade/UI/Http/CommandMappers/SellItemCommandMapper.php ================================================ user(); return new SellItemCommand( CharacterId::fromString($userModel->character->getId()), StoreId::fromString((string)$request->route('store')), ItemId::fromString((string)$request->route('item')) ); } } ================================================ FILE: app/Modules/User/Application/Commands/CreateUserCommand.php ================================================ name = $name; $this->email = $email; $this->password = $password; } /** * @return string */ public function getName(): string { return $this->name; } /** * @return string */ public function getEmail(): string { return $this->email; } /** * @return string */ public function getPassword(): string { return $this->password; } } ================================================ FILE: app/Modules/User/Application/Services/UserService.php ================================================ create([ 'name' => $command->getName(), 'email' => $command->getEmail(), 'password' => $command->getPassword(), ]); return $user; } } ================================================ FILE: app/Modules/User/UI/Http/CommandMappers/CreateUserCommandMapper.php ================================================ registerRepositoryInterfaces(); } protected function registerRepositoryInterfaces(): self { $this->app->bind( StoreRepositoryInterface::class, StoreRepository::class ); $this->app->bind( InventoryRepositoryInterface::class, InventoryRepository::class ); $this->app->bind( LocationRepositoryInterface::class, LocationRepository::class ); $this->app->bind( RaceRepositoryInterface::class, RaceRepository::class ); $this->app->bind( CharacterRepositoryInterface::class, CharacterRepository::class ); $this->app->bind( ItemPrototypeRepositoryInterface::class, ItemPrototypeRepository::class ); $this->app->bind( ItemRepositoryInterface::class, ItemRepository::class ); $this->app->bind( BattleRepositoryInterface::class, BattleRepository::class ); $this->app->bind( MessageRepositoryInterface::class, MessageRepository::class ); $this->app->bind( ImageRepositoryInterface::class, ImageRepository::class ); return $this; } } ================================================ FILE: app/Providers/AuthServiceProvider.php ================================================ 'App\Policies\ModelPolicy', ]; /** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); } } ================================================ FILE: app/Providers/BroadcastServiceProvider.php ================================================ [ 'App\Listeners\EventListener', ], ]; /** * Register any events for your application. * * @return void */ public function boot() { // } } ================================================ FILE: app/Providers/RouteServiceProvider.php ================================================ configureRateLimiting(); $this->routes(function () { Route::prefix('api') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); }); } /** * Configure the rate limiters for the application. * * @return void */ protected function configureRateLimiting() { RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); }); } } ================================================ FILE: app/Providers/ViewComposerServiceProvider.php ================================================ toString(); } } ================================================ FILE: app/Traits/ThrowsDice.php ================================================ make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, new Symfony\Component\Console\Output\ConsoleOutput ); /* |-------------------------------------------------------------------------- | Shutdown The Application |-------------------------------------------------------------------------- | | Once Artisan has finished running, we will fire off the shutdown events | so that any final work may be done by the application before we shut | down the process. This is the last thing to happen to the request. | */ $kernel->terminate($input, $status); exit($status); ================================================ FILE: bootstrap/app.php ================================================ singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class ); $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class ); /* |-------------------------------------------------------------------------- | Return The Application |-------------------------------------------------------------------------- | | This script returns the application instance. The instance is given to | the calling script so we can separate the building of the instances | from the actual running of the application and sending responses. | */ return $app; ================================================ FILE: bootstrap/cache/.gitignore ================================================ * !.gitignore ================================================ FILE: composer.json ================================================ { "name": "mchekin/rpg", "type": "project", "description": "Online Role Playing Game.", "keywords": [ "game", "rpg-game", "browser-game", "mmorpg", "mmo", "roleplaying-game" ], "license": "MIT", "require": { "php": "^7.3|^8.0", "ext-json": "*", "fideloper/proxy": "^4.4", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", "intervention/image": "^2.4", "laravel/framework": "^8.12", "laravel/legacy-factories": "^1.1", "laravel/tinker": "^2.5", "laravel/ui": "^3.0", "tcg/voyager": "^1.4" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.2", "facade/ignition": "^2.5", "fakerphp/faker": "^1.9.1", "laravel/sail": "^1.0.1", "laravel/dusk": "^6.0", "mockery/mockery": "^1.4.2", "nunomaduro/collision": "^5.0", "phpunit/phpunit": "^9.3.3" }, "autoload": { "files": [ "app/Support/helpers.php" ], "psr-4": { "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "scripts": { "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" ], "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ "@php artisan key:generate --ansi" ] }, "repositories": { "hooks": { "type": "composer", "url": "https://larapack.io" } }, "extra": { "laravel": { "dont-discover": [] } }, "config": { "optimize-autoloader": true, "preferred-install": "dist", "sort-packages": true }, "minimum-stability": "dev", "prefer-stable": true } ================================================ FILE: config/app.php ================================================ env('APP_NAME', 'Laravel'), /* |-------------------------------------------------------------------------- | Application Environment |-------------------------------------------------------------------------- | | This value determines the "environment" your application is currently | running in. This may determine how you prefer to configure various | services the application utilizes. Set this in your ".env" file. | */ 'env' => env('APP_ENV', 'production'), /* |-------------------------------------------------------------------------- | Application Debug Mode |-------------------------------------------------------------------------- | | When your application is in debug mode, detailed error messages with | stack traces will be shown on every error that occurs within your | application. If disabled, a simple generic error page is shown. | */ 'debug' => (bool) env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- | Application URL |-------------------------------------------------------------------------- | | This URL is used by the console to properly generate URLs when using | the Artisan command line tool. You should set this to the root of | your application so that it is used when running Artisan tasks. | */ 'url' => env('APP_URL', 'http://localhost'), 'asset_url' => env('ASSET_URL', null), /* |-------------------------------------------------------------------------- | Application Timezone |-------------------------------------------------------------------------- | | Here you may specify the default timezone for your application, which | will be used by the PHP date and date-time functions. We have gone | ahead and set this to a sensible default for you out of the box. | */ 'timezone' => 'UTC', /* |-------------------------------------------------------------------------- | Application Locale Configuration |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used | by the translation service provider. You are free to set this value | to any of the locales which will be supported by the application. | */ 'locale' => 'en', /* |-------------------------------------------------------------------------- | Application Fallback Locale |-------------------------------------------------------------------------- | | The fallback locale determines the locale to use when the current one | is not available. You may change the value to correspond to any of | the language folders that are provided through your application. | */ 'fallback_locale' => 'en', /* |-------------------------------------------------------------------------- | Faker Locale |-------------------------------------------------------------------------- | | This locale will be used by the Faker PHP library when generating fake | data for your database seeds. For example, this will be used to get | localized telephone numbers, street address information and more. | */ 'faker_locale' => 'en_US', /* |-------------------------------------------------------------------------- | Encryption Key |-------------------------------------------------------------------------- | | This key is used by the Illuminate encrypter service and should be set | to a random, 32 character string, otherwise these encrypted strings | will not be safe. Please do this before deploying an application! | */ 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', /* |-------------------------------------------------------------------------- | Autoloaded Service Providers |-------------------------------------------------------------------------- | | The service providers listed here will be automatically loaded on the | request to your application. Feel free to add your own services to | this array to grant expanded functionality to your applications. | */ 'providers' => [ /* * Laravel Framework Service Providers... */ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Bus\BusServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class, Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, Illuminate\Cookie\CookieServiceProvider::class, Illuminate\Database\DatabaseServiceProvider::class, Illuminate\Encryption\EncryptionServiceProvider::class, Illuminate\Filesystem\FilesystemServiceProvider::class, Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, /* * Package Service Providers... */ /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, \App\Providers\ViewComposerServiceProvider::class, ], /* |-------------------------------------------------------------------------- | Class Aliases |-------------------------------------------------------------------------- | | This array of class aliases will be registered when this application | is started. However, feel free to register as many as you wish as | the aliases are "lazy" loaded so they don't hinder performance. | */ 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, 'Arr' => Illuminate\Support\Arr::class, 'Artisan' => Illuminate\Support\Facades\Artisan::class, 'Auth' => Illuminate\Support\Facades\Auth::class, 'Blade' => Illuminate\Support\Facades\Blade::class, 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, 'Bus' => Illuminate\Support\Facades\Bus::class, 'Cache' => Illuminate\Support\Facades\Cache::class, 'Config' => Illuminate\Support\Facades\Config::class, 'Cookie' => Illuminate\Support\Facades\Cookie::class, 'Crypt' => Illuminate\Support\Facades\Crypt::class, 'Date' => Illuminate\Support\Facades\Date::class, 'DB' => Illuminate\Support\Facades\DB::class, 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'File' => Illuminate\Support\Facades\File::class, 'Gate' => Illuminate\Support\Facades\Gate::class, 'Hash' => Illuminate\Support\Facades\Hash::class, 'Http' => Illuminate\Support\Facades\Http::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, 'Notification' => Illuminate\Support\Facades\Notification::class, 'Password' => Illuminate\Support\Facades\Password::class, 'Queue' => Illuminate\Support\Facades\Queue::class, 'Redirect' => Illuminate\Support\Facades\Redirect::class, // 'Redis' => Illuminate\Support\Facades\Redis::class, 'Request' => Illuminate\Support\Facades\Request::class, 'Response' => Illuminate\Support\Facades\Response::class, 'Route' => Illuminate\Support\Facades\Route::class, 'Schema' => Illuminate\Support\Facades\Schema::class, 'Session' => Illuminate\Support\Facades\Session::class, 'Storage' => Illuminate\Support\Facades\Storage::class, 'Str' => Illuminate\Support\Str::class, 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, ], ]; ================================================ FILE: config/auth.php ================================================ [ 'guard' => 'web', 'passwords' => 'users', 'hash' => false, ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | here which uses session storage and the Eloquent user provider. | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | Supported: "session", "token" | */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'session', 'provider' => 'users', ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | If you have multiple user tables or models you may configure multiple | sources which represent each model / table. These sources may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => \App\Models\User::class, ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | You may specify multiple password reset configurations if you have more | than one user table or model in the application and you want to have | separate password reset settings based on the specific user types. | | The expire time is the number of minutes that the reset token should be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], ], /* |-------------------------------------------------------------------------- | Password Confirmation Timeout |-------------------------------------------------------------------------- | | Here you may define the amount of seconds before a password confirmation | times out and the user is prompted to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | */ 'password_timeout' => 10800, ]; ================================================ FILE: config/broadcasting.php ================================================ env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- | Broadcast Connections |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used | to broadcast events to other systems or over websockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'useTLS' => true, ], ], 'ably' => [ 'driver' => 'ably', 'key' => env('ABLY_KEY'), ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], 'log' => [ 'driver' => 'log', ], 'null' => [ 'driver' => 'null', ], ], ]; ================================================ FILE: config/cache.php ================================================ env('CACHE_DRIVER', 'file'), /* |-------------------------------------------------------------------------- | Cache Stores |-------------------------------------------------------------------------- | | Here you may define all of the cache "stores" for your application as | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | | Supported drivers: "apc", "array", "database", "file", | "memcached", "redis", "dynamodb", "null" | */ 'stores' => [ 'apc' => [ 'driver' => 'apc', ], 'array' => [ 'driver' => 'array', 'serialize' => false, ], 'database' => [ 'driver' => 'database', 'table' => 'cache', 'connection' => null, 'lock_connection' => null, ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), ], 'memcached' => [ 'driver' => 'memcached', 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 'sasl' => [ env('MEMCACHED_USERNAME'), env('MEMCACHED_PASSWORD'), ], 'options' => [ // Memcached::OPT_CONNECT_TIMEOUT => 2000, ], 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'cache', 'lock_connection' => 'default', ], 'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'), ], ], /* |-------------------------------------------------------------------------- | Cache Key Prefix |-------------------------------------------------------------------------- | | When utilizing a RAM based store such as APC or Memcached, there might | be other applications utilizing the same cache. So, we'll specify a | value to get prefixed to all our keys so we can avoid collisions. | */ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), ]; ================================================ FILE: config/cors.php ================================================ ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => ['*'], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => false, ]; ================================================ FILE: config/database.php ================================================ env('DB_CONNECTION', 'mysql'), /* |-------------------------------------------------------------------------- | Database Connections |-------------------------------------------------------------------------- | | Here are each of the database connections setup for your application. | Of course, examples of configuring each database platform that is | supported by Laravel is shown below to make development simple. | | | All database work in Laravel is done through the PHP PDO facilities | so make sure you have the driver for your particular database of | choice installed on your machine before you begin development. | */ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DATABASE_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', false), ], 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'schema' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', ], ], /* |-------------------------------------------------------------------------- | Migration Repository Table |-------------------------------------------------------------------------- | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of | the migrations on disk haven't actually been run in the database. | */ 'migrations' => 'migrations', /* |-------------------------------------------------------------------------- | Redis Databases |-------------------------------------------------------------------------- | | Redis is an open source, fast, and advanced key-value store that also | provides a richer set of commands than a typical key-value systems | such as APC or Memcached. Laravel makes it easy to dig right in. | */ 'redis' => [ 'client' => env('REDIS_CLIENT', 'phpredis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'phpredis'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => 0, ], 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_CACHE_DB', 1), ], ], ]; ================================================ FILE: config/filesystems.php ================================================ env('FILESYSTEM_DRIVER', 'local'), /* |-------------------------------------------------------------------------- | Filesystem Disks |-------------------------------------------------------------------------- | | Here you may configure as many filesystem "disks" as you wish, and you | may even configure multiple disks of the same driver. Defaults have | been setup for each driver as an example of the required options. | | Supported Drivers: "local", "ftp", "sftp", "s3" | */ 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), ], 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL').'/storage', 'visibility' => 'public', ], 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), ], ], /* |-------------------------------------------------------------------------- | Symbolic Links |-------------------------------------------------------------------------- | | Here you may configure the symbolic links that will be created when the | `storage:link` Artisan command is executed. The array keys should be | the locations of the links and the values should be their targets. | */ 'links' => [ public_path('storage') => storage_path('app/public'), ], 'max_size_in_bytes' => min( ini_size_to_bytes(ini_get('post_max_size')), ini_size_to_bytes(ini_get('upload_max_filesize')), ini_size_to_bytes(env('MAX_FILE_SIZE','2M')) ), ]; ================================================ FILE: config/hashing.php ================================================ 'bcrypt', /* |-------------------------------------------------------------------------- | Bcrypt Options |-------------------------------------------------------------------------- | | Here you may specify the configuration options that should be used when | passwords are hashed using the Bcrypt algorithm. This will allow you | to control the amount of time it takes to hash the given password. | */ 'bcrypt' => [ 'rounds' => env('BCRYPT_ROUNDS', 10), ], /* |-------------------------------------------------------------------------- | Argon Options |-------------------------------------------------------------------------- | | Here you may specify the configuration options that should be used when | passwords are hashed using the Argon algorithm. These will allow you | to control the amount of time it takes to hash the given password. | */ 'argon' => [ 'memory' => 1024, 'threads' => 2, 'time' => 2, ], ]; ================================================ FILE: config/hooks.php ================================================ env('HOOKS_ENABLED', true), ]; ================================================ FILE: config/image.php ================================================ 'gd' ]; ================================================ FILE: config/logging.php ================================================ env('LOG_CHANNEL', 'stack'), /* |-------------------------------------------------------------------------- | Log Channels |-------------------------------------------------------------------------- | | Here you may configure the log channels for your application. Out of | the box, Laravel uses the Monolog PHP logging library. This gives | you a variety of powerful log handlers / formatters to utilize. | | Available Drivers: "single", "daily", "slack", "syslog", | "errorlog", "monolog", | "custom", "stack" | */ 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => env('LOG_LEVEL', 'critical'), ], 'papertrail' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => SyslogUdpHandler::class, 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), ], ], 'stderr' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', ], ], 'syslog' => [ 'driver' => 'syslog', 'level' => env('LOG_LEVEL', 'debug'), ], 'errorlog' => [ 'driver' => 'errorlog', 'level' => env('LOG_LEVEL', 'debug'), ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], ], ]; ================================================ FILE: config/mail.php ================================================ env('MAIL_MAILER', 'smtp'), /* |-------------------------------------------------------------------------- | Mailer Configurations |-------------------------------------------------------------------------- | | Here you may configure all of the mailers used by your application plus | their respective settings. Several examples have been configured for | you and you are free to add your own as your application requires. | | Laravel supports a variety of mail "transport" drivers to be used while | sending an e-mail. You will specify which one you are using for your | mailers below. You are free to add additional mailers as required. | | Supported: "smtp", "sendmail", "mailgun", "ses", | "postmark", "log", "array" | */ 'mailers' => [ 'smtp' => [ 'transport' => 'smtp', 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 'port' => env('MAIL_PORT', 587), 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, 'auth_mode' => null, ], 'ses' => [ 'transport' => 'ses', ], 'mailgun' => [ 'transport' => 'mailgun', ], 'postmark' => [ 'transport' => 'postmark', ], 'sendmail' => [ 'transport' => 'sendmail', 'path' => '/usr/sbin/sendmail -bs', ], 'log' => [ 'transport' => 'log', 'channel' => env('MAIL_LOG_CHANNEL'), ], 'array' => [ 'transport' => 'array', ], ], /* |-------------------------------------------------------------------------- | Global "From" Address |-------------------------------------------------------------------------- | | You may wish for all e-mails sent by your application to be sent from | the same address. Here, you may specify a name and address that is | used globally for all e-mails that are sent by your application. | */ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 'name' => env('MAIL_FROM_NAME', 'Example'), ], /* |-------------------------------------------------------------------------- | Markdown Mail Settings |-------------------------------------------------------------------------- | | If you are using Markdown based email rendering, you may configure your | theme and component paths here, allowing you to customize the design | of the emails. Or, you may simply stick with the Laravel defaults! | */ 'markdown' => [ 'theme' => 'default', 'paths' => [ resource_path('views/vendor/mail'), ], ], ]; ================================================ FILE: config/queue.php ================================================ env('QUEUE_CONNECTION', 'sync'), /* |-------------------------------------------------------------------------- | Queue Connections |-------------------------------------------------------------------------- | | Here you may configure the connection information for each server that | is used by your application. A default configuration has been added | for each back-end shipped with Laravel. You are free to add more. | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" | */ 'connections' => [ 'sync' => [ 'driver' => 'sync', ], 'database' => [ 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', 'retry_after' => 90, 'after_commit' => false, ], 'beanstalkd' => [ 'driver' => 'beanstalkd', 'host' => 'localhost', 'queue' => 'default', 'retry_after' => 90, 'block_for' => 0, 'after_commit' => false, ], 'sqs' => [ 'driver' => 'sqs', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'queue' => env('SQS_QUEUE', 'default'), 'suffix' => env('SQS_SUFFIX'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'after_commit' => false, ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 90, 'block_for' => null, 'after_commit' => false, ], ], /* |-------------------------------------------------------------------------- | Failed Queue Jobs |-------------------------------------------------------------------------- | | These options configure the behavior of failed queue job logging so you | can control which database and table are used to store the jobs that | have failed. You may change them to any database / table you wish. | */ 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 'database' => env('DB_CONNECTION', 'mysql'), 'table' => 'failed_jobs', ], ]; ================================================ FILE: config/services.php ================================================ [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), ], 'postmark' => [ 'token' => env('POSTMARK_TOKEN'), ], 'ses' => [ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], 'sparkpost' => [ 'secret' => env('SPARKPOST_SECRET'), ], ]; ================================================ FILE: config/session.php ================================================ env('SESSION_DRIVER', 'file'), /* |-------------------------------------------------------------------------- | Session Lifetime |-------------------------------------------------------------------------- | | Here you may specify the number of minutes that you wish the session | to be allowed to remain idle before it expires. If you want them | to immediately expire on the browser closing, set that option. | */ 'lifetime' => env('SESSION_LIFETIME', 120), 'expire_on_close' => false, /* |-------------------------------------------------------------------------- | Session Encryption |-------------------------------------------------------------------------- | | This option allows you to easily specify that all of your session data | should be encrypted before it is stored. All encryption will be run | automatically by Laravel and you can use the Session like normal. | */ 'encrypt' => false, /* |-------------------------------------------------------------------------- | Session File Location |-------------------------------------------------------------------------- | | When using the native session driver, we need a location where session | files may be stored. A default has been set for you but a different | location may be specified. This is only needed for file sessions. | */ 'files' => storage_path('framework/sessions'), /* |-------------------------------------------------------------------------- | Session Database Connection |-------------------------------------------------------------------------- | | When using the "database" or "redis" session drivers, you may specify a | connection that should be used to manage these sessions. This should | correspond to a connection in your database configuration options. | */ 'connection' => env('SESSION_CONNECTION', null), /* |-------------------------------------------------------------------------- | Session Database Table |-------------------------------------------------------------------------- | | When using the "database" session driver, you may specify the table we | should use to manage the sessions. Of course, a sensible default is | provided for you; however, you are free to change this as needed. | */ 'table' => 'sessions', /* |-------------------------------------------------------------------------- | Session Cache Store |-------------------------------------------------------------------------- | | While using one of the framework's cache driven session backends you may | list a cache store that should be used for these sessions. This value | must match with one of the application's configured cache "stores". | | Affects: "apc", "dynamodb", "memcached", "redis" | */ 'store' => env('SESSION_STORE', null), /* |-------------------------------------------------------------------------- | Session Sweeping Lottery |-------------------------------------------------------------------------- | | Some session drivers must manually sweep their storage location to get | rid of old sessions from storage. Here are the chances that it will | happen on a given request. By default, the odds are 2 out of 100. | */ 'lottery' => [2, 100], /* |-------------------------------------------------------------------------- | Session Cookie Name |-------------------------------------------------------------------------- | | Here you may change the name of the cookie used to identify a session | instance by ID. The name specified here will get used every time a | new session cookie is created by the framework for every driver. | */ 'cookie' => env( 'SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_').'_session' ), /* |-------------------------------------------------------------------------- | Session Cookie Path |-------------------------------------------------------------------------- | | The session cookie path determines the path for which the cookie will | be regarded as available. Typically, this will be the root path of | your application but you are free to change this when necessary. | */ 'path' => '/', /* |-------------------------------------------------------------------------- | Session Cookie Domain |-------------------------------------------------------------------------- | | Here you may change the domain of the cookie used to identify a session | in your application. This will determine which domains the cookie is | available to in your application. A sensible default has been set. | */ 'domain' => env('SESSION_DOMAIN', null), /* |-------------------------------------------------------------------------- | HTTPS Only Cookies |-------------------------------------------------------------------------- | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep | the cookie from being sent to you if it can not be done securely. | */ 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- | HTTP Access Only |-------------------------------------------------------------------------- | | Setting this value to true will prevent JavaScript from accessing the | value of the cookie and the cookie will only be accessible through | the HTTP protocol. You are free to modify this option if needed. | */ 'http_only' => true, /* |-------------------------------------------------------------------------- | Same-Site Cookies |-------------------------------------------------------------------------- | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we | will set this value to "lax" since this is a secure default value. | | Supported: "lax", "strict", "none", null | */ 'same_site' => 'lax', ]; ================================================ FILE: config/view.php ================================================ [ resource_path('views'), ], /* |-------------------------------------------------------------------------- | Compiled View Path |-------------------------------------------------------------------------- | | This option determines where all the compiled Blade templates will be | stored for your application. Typically, this is within the storage | directory. However, as usual, you are free to change this value. | */ 'compiled' => realpath(storage_path('framework/views')), ]; ================================================ FILE: config/voyager-hooks.php ================================================ env('HOOKS_ENABLED', true), 'add-route' => true, 'add-hook-menu-item' => true, 'add-hook-permissions' => true, 'publish-vendor-files' => true, ]; ================================================ FILE: config/voyager.php ================================================ [ 'add_default_role_on_register' => true, 'default_role' => 'user', // Set `namespace` to `null` to use `config('auth.providers.users.model')` value // Set `namespace` to a class to override auth user model. // However make sure the appointed class must ready to use before installing voyager. // Otherwise `php artisan voyager:install` will fail with class not found error. 'namespace' => null, 'default_avatar' => 'users/default.png', 'redirect' => '/admin', ], /* |-------------------------------------------------------------------------- | Controllers config |-------------------------------------------------------------------------- | | Here you can specify voyager controller settings | */ 'controllers' => [ 'namespace' => 'TCG\\Voyager\\Http\\Controllers', ], /* |-------------------------------------------------------------------------- | Models config |-------------------------------------------------------------------------- | | Here you can specify default model namespace when creating BREAD. | Must include trailing backslashes. If not defined the default application | namespace will be used. | */ 'models' => [ //'namespace' => 'App\\', ], /* |-------------------------------------------------------------------------- | Storage Config |-------------------------------------------------------------------------- | | Here you can specify attributes related to your application file system | */ 'storage' => [ 'disk' => env('FILESYSTEM_DRIVER', 'public'), ], /* |-------------------------------------------------------------------------- | Media Manager |-------------------------------------------------------------------------- | | Here you can specify if media manager can show hidden files like(.gitignore) | */ 'hidden_files' => false, /* |-------------------------------------------------------------------------- | Database Config |-------------------------------------------------------------------------- | | Here you can specify voyager database settings | */ 'database' => [ 'tables' => [ 'hidden' => ['migrations', 'data_rows', 'data_types', 'menu_items', 'password_resets', 'permission_role', 'settings'], ], 'autoload_migrations' => true, ], /* |-------------------------------------------------------------------------- | Multilingual configuration |-------------------------------------------------------------------------- | | Here you can specify if you want Voyager to ship with support for | multilingual and what locales are enabled. | */ 'multilingual' => [ /* * Set whether or not the multilingual is supported by the BREAD input. */ 'enabled' => false, /* * Select default language */ 'default' => 'en', /* * Select languages that are supported. */ 'locales' => [ 'en', //'pt', ], ], /* |-------------------------------------------------------------------------- | Dashboard config |-------------------------------------------------------------------------- | | Here you can modify some aspects of your dashboard | */ 'dashboard' => [ // Add custom list items to navbar's dropdown 'navbar_items' => [ 'voyager::generic.profile' => [ 'route' => 'voyager.profile', 'classes' => 'class-full-of-rum', 'icon_class' => 'voyager-person', ], 'voyager::generic.home' => [ 'route' => '/', 'icon_class' => 'voyager-home', 'target_blank' => true, ], 'voyager::generic.logout' => [ 'route' => 'voyager.logout', 'icon_class' => 'voyager-power', ], ], 'widgets' => [ ], ], /* |-------------------------------------------------------------------------- | Automatic Procedures |-------------------------------------------------------------------------- | | When a change happens on Voyager, we can automate some routines. | */ 'bread' => [ // When a BREAD is added, create the Menu item using the BREAD properties. 'add_menu_item' => true, // which menu add item to 'default_menu' => 'admin', // When a BREAD is added, create the related Permission. 'add_permission' => true, // which role add premissions to 'default_role' => 'admin', ], /* |-------------------------------------------------------------------------- | UI Generic Config |-------------------------------------------------------------------------- | | Here you change some of the Voyager UI settings. | */ 'primary_color' => '#22A7F0', 'show_dev_tips' => true, // Show development tip "How To Use:" in Menu and Settings // Here you can specify additional assets you would like to be included in the master.blade 'additional_css' => [ //'css/custom.css', ], 'additional_js' => [ //'js/custom.js', ], 'googlemaps' => [ 'key' => env('GOOGLE_MAPS_KEY', ''), 'center' => [ 'lat' => env('GOOGLE_MAPS_DEFAULT_CENTER_LAT', '32.715738'), 'lng' => env('GOOGLE_MAPS_DEFAULT_CENTER_LNG', '-117.161084'), ], 'zoom' => env('GOOGLE_MAPS_DEFAULT_ZOOM', 11), ], /* |-------------------------------------------------------------------------- | Model specific settings |-------------------------------------------------------------------------- | | Here you change some model specific settings | */ 'settings' => [ // Enables Laravel cache method for // storing cache values between requests 'cache' => false, ], // Activate compass when environment is NOT local 'compass_in_production' => false, 'media' => [ // The allowed mimetypes to be uploaded through the media-manager. 'allowed_mimetypes' => '*', //All types can be uploaded /* 'allowed_mimetypes' => [ 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'video/mp4', ], */ //Path for media-manager. Relative to the filesystem. 'path' => '/', 'show_folders' => true, 'allow_upload' => true, 'allow_move' => true, 'allow_delete' => true, 'allow_create_folder' => true, 'allow_rename' => true, /*'watermark' => [ 'source' => 'watermark.png', 'position' => 'bottom-left', 'x' => 0, 'y' => 0, 'size' => 15, ], 'thumbnails' => [ [ 'type' => 'fit', 'name' => 'fit-500', 'width' => 500, 'height'=> 500 ], ]*/ ], ]; ================================================ FILE: database/.gitignore ================================================ *.sqlite ================================================ FILE: database/factories/UserFactory.php ================================================ define(User::class, static function (Generator $faker) { static $password; return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => $password ?: $password = bcrypt('secret'), 'remember_token' => Str::random(60) ]; }); $factory->define(Character::class, static function (Generator $faker) { /** @var CharacterRepositoryInterface $characterRepository */ $characterRepository = resolve(CharacterRepositoryInterface::class); /** @var Race $raceModel */ $raceModel = Race::query()->inRandomOrder()->first(); $location = Location::query()->inRandomOrder()->first(); $race = (new RaceRepository())->getOne($raceModel->getId()); $hitPoints = HitPoints::byRace($race); $genders = ['male', 'female']; $types = CharacterType::TYPES; $characterId = $characterRepository->nextIdentity()->toString(); return [ 'id' => $characterId, 'race_id' => $race->getId(), 'level_id' => 1, 'location_id' => $location, 'name' => $faker->name, 'gender' => $genders[array_rand($genders)], 'type' => $characterType = $types[array_rand($types)], 'xp' => 0, 'reputation' => 0, // attributes 'strength' => $race->getStrength(), 'agility' => $race->getAgility(), 'constitution' => $race->getConstitution(), 'intelligence' => $race->getIntelligence(), 'charisma' => $race->getCharisma(), 'hit_points' => $hitPoints->getCurrentHitPoints(), 'total_hit_points' => $hitPoints->getMaximumHitPoints(), 'user_id' => static function () use ($characterType) { return $characterType === CharacterType::PLAYER ? factory(User::class)->create()->id : null; }, ]; }); $factory->afterCreating(Character::class, static function (Character $character) { /** @var InventoryRepositoryInterface $inventoryRepository */ $inventoryRepository = resolve(InventoryRepositoryInterface::class); /** @var StoreRepositoryInterface $storeRepository */ $storeRepository = resolve(StoreRepositoryInterface::class); Inventory::query()->create([ 'id' => $inventoryRepository->nextIdentity()->toString(), 'character_id' => $character->getId(), 'money' => random_int(0, 5000), ]); Store::query()->create([ 'id' => $storeRepository->nextIdentity()->toString(), 'character_id' => $character->getId(), 'money' => random_int(0, 5000), 'type' => $character->isMerchant() ? StoreType::BUY_AND_SELL : StoreType::SELL_ONLY, ]); }); $factory->define(Item::class, static function () { static $charactersIds = []; /** @var ItemRepositoryInterface $itemRepository */ $itemRepository = resolve(ItemRepositoryInterface::class); /** @var ItemPrototype $itemPrototype */ $itemPrototype = ItemPrototype::query()->inRandomOrder()->first(); /** @var Character $character */ $character = Character::query()->whereNotIn('id', $charactersIds)->first(); $charactersIds[] = $character->getId(); $itemId = $itemRepository->nextIdentity()->toString(); return [ 'id' => $itemId, 'name' => $itemPrototype->getName(), 'description' => $itemPrototype->getDescription(), 'effects' => $itemPrototype->getEffects(), 'price' => $itemPrototype->getPrice(), 'type' => $itemPrototype->getType(), 'image_file_path' => $itemPrototype->getImageFilePath(), 'prototype_id' => $itemPrototype->getId(), 'creator_character_id' => $character->getId(), ]; }); $factory->afterCreating(Item::class, static function (Item $item) { static $charactersIds = []; /** @var Character $character */ $character = Character::query()->whereNotIn('id', $charactersIds)->first(); $charactersIds[] = $character->getId(); $character->inventory->items()->attach($item->getId(), [ 'inventory_slot_number' => 0, 'status' => ItemStatus::EQUIPPED, ]); }); $factory->afterCreating(Item::class, static function (Item $item) { static $charactersIds = []; /** @var Character $character */ $character = Character::query()->whereNotIn('id', $charactersIds)->first(); $charactersIds[] = $character->getId(); $character->store->items()->attach($item->getId(), [ 'inventory_slot_number' => 0, ]); }); ================================================ FILE: database/migrations/2014_10_12_000000_create_users_table.php ================================================ id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } } ================================================ FILE: database/migrations/2014_10_12_100000_create_password_resets_table.php ================================================ string('email')->index(); $table->string('token'); $table->timestamp('created_at')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('password_resets'); } } ================================================ FILE: database/migrations/2017_05_14_055744_create_locations_table.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->string('name')->unique(); $table->string('description'); $table->string('image'); $table->string('image_sm'); $table->timestamps(); }); Schema::create('adjacent_location', static function (Blueprint $table) { $table->uuid('location_id')->index(); $table->uuid('adjacent_location_id')->index(); $table->primary(['location_id', 'adjacent_location_id']); $table->enum('direction', Location::getDirections()); $table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade'); $table->foreign('adjacent_location_id')->references('id')->on('locations')->onDelete('cascade'); $table->timestamps(); }); /** @var LocationRepositoryInterface $locationRepository */ $locationRepository = resolve(LocationRepositoryInterface::class); $locations = [ [ 'id' => $locationRepository->nextIdentity()->toString(), 'name' => 'Inn', 'description' => 'An establishment or building providing lodging and, usually, food and drink for travelers (Starting location)', 'image' => 'locations/Inn-800px.png', 'image_sm' => 'locations/Inn-300px.png', ], array( 'id' => $locationRepository->nextIdentity()->toString(), 'name' => 'Town Hall', 'description' => 'Public forum or meeting in which those attending gather to discuss civic or political issues, hear and ask questions about the ideas of a candidate for public office', 'image' => 'locations/Townhall-800px.png', 'image_sm' => 'locations/Townhall-300px.png', ), [ 'id' => $locationRepository->nextIdentity()->toString(), 'name' => 'Smithy', 'description' => "A blacksmith's shop. A place to purchase weaponry and armor or train one's skill as a blacksmith", 'image' => 'locations/Blacksmith-800px.png', 'image_sm' => 'locations/Blacksmith-300px.png', ], [ 'id' => $locationRepository->nextIdentity()->toString(), 'name' => 'Military academy fortress', 'description' => 'An institute where soldiers and mercenaries train they martial skills', 'image' => 'locations/Fortress-800px.png', 'image_sm' => 'locations/Fortress-300px.png', ], ]; foreach ($locations as $location) { Location::query()->forceCreate($location); } $adjacent_locations = [ [ 'location_id' => $locations[0]['id'], 'adjacent_location_id' => $locations[1]['id'], 'direction' => 'north', ], [ 'location_id' => $locations[0]['id'], 'adjacent_location_id' => $locations[2]['id'], 'direction' => 'east', ], [ 'location_id' => $locations[0]['id'], 'adjacent_location_id' => $locations[3]['id'], 'direction' => 'south', ], ]; foreach ($adjacent_locations as $record) { /** @var $location Location */ $location = Location::query()->find($record['location_id']); /** @var $adjacent_location Location */ $adjacent_location = Location::query()->find($record['adjacent_location_id']); $location->addAdjacentLocation($adjacent_location, $record['direction']); } } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('adjacent_location'); Schema::dropIfExists('locations'); } } ================================================ FILE: database/migrations/2017_05_14_055823_create_races_table.php ================================================ integer('id')->unsigned()->primary(); $table->string("name"); $table->string("description"); $table->string("male_image"); $table->string("female_image"); // attributes $table->integer('strength'); $table->integer('agility'); $table->integer('constitution'); $table->integer('intelligence'); $table->integer('charisma'); // locations $table->uuid('starting_location_id'); $table->foreign('starting_location_id') ->references('id') ->on('locations') ->onDelete('restrict'); $table->timestamps(); }); /** @var Location $location */ $location = Location::query()->firstOrFail(); $races = [ [ "id" => 1, "name" => "Human", "description" => "This race combines in itself all the properties of the other races, albeit they are less pronounced.", "male_image" => "images/races/human-male.png", "female_image" => "images/races/human-female.png", "strength" => 5, "agility" => 5, "constitution" => 5, "intelligence" => 5, "charisma" => 1, "starting_location_id" => $location->getId(), ], [ "id" => 2, "name" => "Elf", "description" => "This race is known for it's great agility, but lacks constitution.", "male_image" => "images/races/elf-male.png", "female_image" => "images/races/elf-female.png", "strength" => 5, "agility" => 9, "constitution" => 1, "intelligence" => 5, "charisma" => 1, "starting_location_id" => $location->getId(), ], [ "id" => 3, "name" => "Dwarf", "description" => "This race is known for it's constitution and resilience, but lack agility and grace.", "male_image" => "images/races/dwarf-male.png", "female_image" => "images/races/dwarf-female.png", "strength" => 5, "agility" => 1, "constitution" => 9, "intelligence" => 5, "charisma" => 1, "starting_location_id" => $location->getId(), ], [ "id" => 4, "name" => "Orc", "description" => "This race enjoys great physical strength, but lacks intelligence.", "male_image" => "images/races/orc-male.png", "female_image" => "images/races/orc-female.png", "strength" => 9, "agility" => 5, "constitution" => 5, "intelligence" => 1, "charisma" => 1, "starting_location_id" => $location->getId(), ], ]; foreach ($races as $race) { Race::query()->forceCreate($race); } } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('races'); } } ================================================ FILE: database/migrations/2017_05_14_055844_create_characters_table.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->string('name')->unique(); $table->enum('gender', ['male', 'female']); $table->enum('type', ['player', 'merchant', 'civilian', 'monster']); $table->unsignedInteger('xp')->default(0); $table->unsignedInteger('available_attribute_points')->default(0); $table->integer('reputation'); // attributes $table->integer('strength'); $table->integer('agility'); $table->integer('constitution'); $table->integer('intelligence'); $table->integer('charisma'); // stats $table->integer('hit_points'); $table->integer('total_hit_points'); // statistics $table->integer('battles_won')->default(0); $table->integer('battles_lost')->default(0); $table->unsignedInteger('level_id'); $table->unsignedBigInteger('user_id')->nullable(); $table->foreign('user_id')->references('id')->on('users')->onDelete('set null'); $table->uuid('location_id'); $table->foreign('location_id')->references('id')->on('locations')->onDelete('restrict'); $table->unsignedInteger('race_id'); $table->foreign('race_id')->references('id')->on('races')->onDelete('restrict'); $table->uuid('profile_picture_id')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('characters'); } } ================================================ FILE: database/migrations/2017_05_16_144929_create_battles_table.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->boolean('seen_by_defender')->default(0); $table->uuid('location_id'); $table->foreign('location_id')->references('id')->on('locations')->onDelete('restrict'); $table->uuid('attacker_id'); $table->foreign('attacker_id')->references('id')->on('characters')->onDelete('restrict'); $table->uuid('defender_id'); $table->foreign('defender_id')->references('id')->on('characters')->onDelete('restrict'); $table->uuid('victor_id')->nullable(); $table->foreign('victor_id')->references('id')->on('characters')->onDelete('restrict'); $table->unsignedInteger('victor_xp_gained')->default(0); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('battles'); } } ================================================ FILE: database/migrations/2017_05_16_181330_create_battle_rounds_table.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->uuid('battle_id'); $table->foreign('battle_id')->references('id')->on('battles')->onDelete('restrict'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('battle_rounds'); } } ================================================ FILE: database/migrations/2017_05_16_181844_create_battle_turns_table.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->integer('damageDone')->default(0); $table->integer('damageAbsorbed')->default(0); $table->enum('result_type', BattleTurnResult::TYPES); $table->uuid('executor_id'); $table->foreign('executor_id')->references('id')->on('characters')->onDelete('restrict'); $table->uuid('target_id'); $table->foreign('target_id')->references('id')->on('characters')->onDelete('restrict'); $table->uuid('battle_round_id'); $table->foreign('battle_round_id')->references('id')->on('battle_rounds')->onDelete('restrict'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('battle_turns'); } } ================================================ FILE: database/migrations/2017_11_26_013050_add_user_role_relationship.php ================================================ bigInteger('role_id')->unsigned()->change(); $table->foreign('role_id')->references('id')->on('roles'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { if (DB::getDriverName() !== 'sqlite') { $table->dropForeign(['role_id']); } }); Schema::table('users', function (Blueprint $table) { $table->bigInteger('role_id')->change(); }); } } ================================================ FILE: database/migrations/2018_06_24_132346_create_messages_table.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->uuid('from_id'); $table->uuid('to_id')->nullable(); $table->text('content'); $table->enum('state', ['unread', 'read'])->default('unread'); $table->timestamps(); $table->softDeletes(); $table->foreign('from_id')->references('id')->on('characters'); $table->foreign('to_id')->references('id')->on('characters'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('messages'); } } ================================================ FILE: database/migrations/2018_11_19_202701_create_images.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->uuid('character_id'); $table->foreign('character_id')->references('id')->on('characters')->onDelete('restrict'); $table->string('file_path_full'); $table->string('file_path_small'); $table->string('file_path_icon'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('images'); } } ================================================ FILE: database/migrations/2019_08_19_000000_create_failed_jobs_table.php ================================================ id(); $table->string('uuid')->unique(); $table->text('connection'); $table->text('queue'); $table->longText('payload'); $table->longText('exception'); $table->timestamp('failed_at')->useCurrent(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('failed_jobs'); } } ================================================ FILE: database/migrations/2019_08_31_182034_create_items_table.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->string('name'); $table->string('description'); $table->json('effects'); $table->integer('price')->default(0); $table->string('image_file_path'); $table->enum('type', ItemType::TYPES)->default(ItemType::MISCELLANEOUS); $table->timestamps(); }); Schema::create('items', static function (Blueprint $table) { $table->uuid('id')->primary(); $table->string('name'); $table->string('description'); $table->json('effects'); $table->integer('price')->default(0); $table->string('image_file_path'); $table->enum('type', ItemType::TYPES)->default(ItemType::MISCELLANEOUS); $table->uuid('prototype_id'); $table->foreign('prototype_id')->references('id')->on('item_prototypes')->onDelete('restrict'); $table->uuid('creator_character_id'); $table->foreign('creator_character_id')->references('id')->on('characters')->onDelete('restrict'); $table->timestamps(); }); $this->createPrototypes(); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('items'); Schema::dropIfExists('item_prototypes'); } /** * @return CreateItemsTable * @throws Exception */ private function createPrototypes(): self { DB::table('item_prototypes')->delete(); /** @var ItemPrototypeRepositoryInterface $itemPrototypeRepository */ $itemPrototypeRepository = resolve(ItemPrototypeRepositoryInterface::class); $prototypes = [ [ 'id' => $itemPrototypeRepository->nextIdentity()->toString(), 'name' => 'Wooden Club', 'description' => 'Simplest weapon. A crude wooden club made from a peace of wood.', 'effects' => [ [ 'quantity' => 1, 'type' => ItemEffect::DAMAGE, ] ], 'price' => 15, 'type' => ItemType::MAIN_HAND, 'image_file_path' => 'images/equipment/main_hand/1club.png', ], [ 'id' => $itemPrototypeRepository->nextIdentity()->toString(), 'name' => 'Reinforced Club', 'description' => 'A wooden club reinforced with metal.', 'effects' => [ [ 'quantity' => 3, 'type' => ItemEffect::DAMAGE, ] ], 'price' => 500, 'type' => ItemType::MAIN_HAND, 'image_file_path' => 'images/equipment/main_hand/2reinforced_club.png', ], [ 'id' => $itemPrototypeRepository->nextIdentity()->toString(), 'name' => 'Wooden Buckler', 'description' => 'A small wooden shield.', 'effects' => [ [ 'quantity' => 2, 'type' => ItemEffect::ARMOR, ] ], 'price' => 20, 'type' => ItemType::OFF_HAND, 'image_file_path' => 'images/equipment/off_hand/buckler.png', ], [ 'id' => $itemPrototypeRepository->nextIdentity()->toString(), 'name' => 'Linen Shirt', 'description' => 'A simple shirt made of linen.', 'effects' => [ [ 'quantity' => 1, 'type' => ItemEffect::ARMOR, ] ], 'price' => 5, 'type' => ItemType::BODY_ARMOR, 'image_file_path' => 'images/equipment/body_armor/linen_shirt.png', ], [ 'id' => $itemPrototypeRepository->nextIdentity()->toString(), 'name' => 'Closed Steel Helmet', 'description' => 'Closed helmet made of steel plates', 'effects' => [ [ 'quantity' => 10, 'type' => ItemEffect::ARMOR, ] ], 'price' => 1000, 'type' => ItemType::HEAD_GEAR, 'image_file_path' => 'images/equipment/head_gear/closed_steel_helmet.png', ], ]; foreach ($prototypes as $prototype) { ItemPrototype::query()->create($prototype); } return $this; } } ================================================ FILE: database/migrations/2020_02_29_205331_create_inventories.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->uuid('character_id')->nullable(); // TODO: refactor character creation to allow creating character record before inventory record // $table->foreign('character_id')->references('id')->on('characters')->onDelete('restrict'); $table->integer('money'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('inventories'); } } ================================================ FILE: database/migrations/2020_02_29_205957_create_inventory_item.php ================================================ smallInteger('inventory_slot_number'); $table->enum('status', ItemStatus::STATUSES)->default(ItemStatus::IN_BACKPACK); $table->uuid('inventory_id')->index(); $table->foreign('inventory_id')->references('id')->on('inventories')->onDelete('cascade'); $table->uuid('item_id')->index(); $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('inventory_item'); } } ================================================ FILE: database/migrations/2020_03_30_133100_create_stores.php ================================================ bigIncrements('auto_id'); $table->uuid('id')->index(); $table->enum('type', StoreType::TYPES)->default(StoreType::SELL_ONLY); $table->uuid('character_id')->nullable(); $table->foreign('character_id')->references('id')->on('characters')->onDelete('restrict'); $table->integer('money'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('stores'); } } ================================================ FILE: database/migrations/2020_03_30_133448_create_store_item.php ================================================ smallInteger('inventory_slot_number'); $table->integer('price')->default(0); $table->uuid('store_id')->index(); $table->foreign('store_id')->references('id')->on('stores')->onDelete('cascade'); $table->uuid('item_id')->index(); $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('store_item'); } } ================================================ FILE: database/seeders/CharacterSeeder.php ================================================ count()) { return; } /** @var CharacterRepositoryInterface $characterRepository */ $characterRepository = resolve(CharacterRepositoryInterface::class); /** @var InventoryRepositoryInterface $inventoryRepository */ $inventoryRepository = resolve(InventoryRepositoryInterface::class); /** @var StoreRepositoryInterface $storeRepository */ $storeRepository = resolve(StoreRepositoryInterface::class); /** @var ItemRepositoryInterface $itemRepository */ $itemRepository = resolve(ItemRepositoryInterface::class); $totalHitPoints = 100; /** @var User $user */ $user = User::query()->first(); /** @var Location $location */ $location = Location::query()->firstOrFail(); /** @var Character $someone */ $someone = Character::query()->create([ 'id' => $characterRepository->nextIdentity()->toString(), 'name' => 'Someone', 'gender' => 'male', 'type' => CharacterType::PLAYER, 'xp' => 0, 'reputation' => 0, 'hit_points' => $totalHitPoints, 'total_hit_points' => $totalHitPoints, 'strength' => 5, 'agility' => 5, 'constitution' => 5, 'intelligence' => 5, 'charisma' => 1, 'level_id' => 1, 'user_id' => $user->getId(), 'location_id' => $location->getId(), 'race_id' => 1, ]); /** @var Inventory $inventory */ $inventory = Inventory::query()->create([ 'id' => $inventoryRepository->nextIdentity()->toString(), 'character_id' => $someone->getId(), 'money' => 100, ]); Store::query()->create([ 'id' => $storeRepository->nextIdentity()->toString(), 'character_id' => $someone->getId(), 'money' => 1000, ]); ItemPrototype::query()->get() ->each(static function (ItemPrototype $weaponPrototype, int $slot) use ($someone, $inventory, $itemRepository) { /** @var Item $item */ $item = Item::query()->create([ 'id' => $itemRepository->nextIdentity()->toString(), 'name' => $weaponPrototype->getName(), 'description' => $weaponPrototype->getDescription(), 'effects' => $weaponPrototype->getEffects(), 'price' => $weaponPrototype->getPrice(), 'type' => $weaponPrototype->getType(), 'image_file_path' => $weaponPrototype->getImageFilePath(), 'prototype_id' => $weaponPrototype->getId(), 'creator_character_id' => $someone->getId(), ]); $inventory->items()->attach($item->getId(), [ 'inventory_slot_number' => $slot, 'status' => $slot ? ItemStatus::IN_BACKPACK : ItemStatus::EQUIPPED, ]); }); factory(Character::class, 50)->create(); factory(Item::class, 50)->create(); } } ================================================ FILE: database/seeders/DataRowsTableSeeder.php ================================================ firstOrFail(); $menuDataType = DataType::where('slug', 'menus')->firstOrFail(); $roleDataType = DataType::where('slug', 'roles')->firstOrFail(); $dataRow = $this->dataRow($userDataType, 'id'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'number', 'display_name' => __('voyager::seeders.data_rows.id'), 'required' => 1, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 1, ])->save(); } $dataRow = $this->dataRow($userDataType, 'name'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'text', 'display_name' => __('voyager::seeders.data_rows.name'), 'required' => 1, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 1, 'order' => 2, ])->save(); } $dataRow = $this->dataRow($userDataType, 'email'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'text', 'display_name' => __('voyager::seeders.data_rows.email'), 'required' => 1, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 1, 'order' => 3, ])->save(); } $dataRow = $this->dataRow($userDataType, 'password'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'password', 'display_name' => __('voyager::seeders.data_rows.password'), 'required' => 1, 'browse' => 0, 'read' => 0, 'edit' => 1, 'add' => 1, 'delete' => 0, 'order' => 4, ])->save(); } $dataRow = $this->dataRow($userDataType, 'remember_token'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'text', 'display_name' => __('voyager::seeders.data_rows.remember_token'), 'required' => 0, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 5, ])->save(); } $dataRow = $this->dataRow($userDataType, 'created_at'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'timestamp', 'display_name' => __('voyager::seeders.data_rows.created_at'), 'required' => 0, 'browse' => 1, 'read' => 1, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 6, ])->save(); } $dataRow = $this->dataRow($userDataType, 'updated_at'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'timestamp', 'display_name' => __('voyager::seeders.data_rows.updated_at'), 'required' => 0, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 7, ])->save(); } $dataRow = $this->dataRow($userDataType, 'avatar'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'image', 'display_name' => __('voyager::seeders.data_rows.avatar'), 'required' => 0, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 1, 'order' => 8, ])->save(); } $dataRow = $this->dataRow($userDataType, 'user_belongsto_role_relationship'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'relationship', 'display_name' => __('voyager::seeders.data_rows.role'), 'required' => 0, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 0, 'details' => [ 'model' => 'TCG\\Voyager\\Models\\Role', 'table' => 'roles', 'type' => 'belongsTo', 'column' => 'role_id', 'key' => 'id', 'label' => 'display_name', 'pivot_table' => 'roles', 'pivot' => 0, ], 'order' => 10, ])->save(); } $dataRow = $this->dataRow($userDataType, 'user_belongstomany_role_relationship'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'relationship', 'display_name' => 'Roles', 'required' => 0, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 0, 'details' => [ 'model' => 'TCG\\Voyager\\Models\\Role', 'table' => 'roles', 'type' => 'belongsToMany', 'column' => 'id', 'key' => 'id', 'label' => 'display_name', 'pivot_table' => 'user_roles', 'pivot' => '1', 'taggable' => '0', ], 'order' => 11, ])->save(); } $dataRow = $this->dataRow($userDataType, 'settings'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'hidden', 'display_name' => 'Settings', 'required' => 0, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 12, ])->save(); } $dataRow = $this->dataRow($menuDataType, 'id'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'number', 'display_name' => __('voyager::seeders.data_rows.id'), 'required' => 1, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 1, ])->save(); } $dataRow = $this->dataRow($menuDataType, 'name'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'text', 'display_name' => __('voyager::seeders.data_rows.name'), 'required' => 1, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 1, 'order' => 2, ])->save(); } $dataRow = $this->dataRow($menuDataType, 'created_at'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'timestamp', 'display_name' => __('voyager::seeders.data_rows.created_at'), 'required' => 0, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 3, ])->save(); } $dataRow = $this->dataRow($menuDataType, 'updated_at'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'timestamp', 'display_name' => __('voyager::seeders.data_rows.updated_at'), 'required' => 0, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 4, ])->save(); } $dataRow = $this->dataRow($roleDataType, 'id'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'number', 'display_name' => __('voyager::seeders.data_rows.id'), 'required' => 1, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 1, ])->save(); } $dataRow = $this->dataRow($roleDataType, 'name'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'text', 'display_name' => __('voyager::seeders.data_rows.name'), 'required' => 1, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 1, 'order' => 2, ])->save(); } $dataRow = $this->dataRow($roleDataType, 'created_at'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'timestamp', 'display_name' => __('voyager::seeders.data_rows.created_at'), 'required' => 0, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 3, ])->save(); } $dataRow = $this->dataRow($roleDataType, 'updated_at'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'timestamp', 'display_name' => __('voyager::seeders.data_rows.updated_at'), 'required' => 0, 'browse' => 0, 'read' => 0, 'edit' => 0, 'add' => 0, 'delete' => 0, 'order' => 4, ])->save(); } $dataRow = $this->dataRow($roleDataType, 'display_name'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'text', 'display_name' => __('voyager::seeders.data_rows.display_name'), 'required' => 1, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 1, 'order' => 5, ])->save(); } $dataRow = $this->dataRow($userDataType, 'role_id'); if (!$dataRow->exists) { $dataRow->fill([ 'type' => 'text', 'display_name' => __('voyager::seeders.data_rows.role'), 'required' => 1, 'browse' => 1, 'read' => 1, 'edit' => 1, 'add' => 1, 'delete' => 1, 'order' => 9, ])->save(); } } /** * [dataRow description]. * * @param [type] $type [description] * @param [type] $field [description] * * @return [type] [description] */ protected function dataRow($type, $field) { return DataRow::firstOrNew([ 'data_type_id' => $type->id, 'field' => $field, ]); } } ================================================ FILE: database/seeders/DataTypesTableSeeder.php ================================================ dataType('slug', 'users'); if (!$dataType->exists) { $dataType->fill([ 'name' => 'users', 'display_name_singular' => __('voyager::seeders.data_types.user.singular'), 'display_name_plural' => __('voyager::seeders.data_types.user.plural'), 'icon' => 'voyager-person', 'model_name' => 'TCG\\Voyager\\Models\\User', 'policy_name' => 'TCG\\Voyager\\Policies\\UserPolicy', 'controller' => 'TCG\\Voyager\\Http\\Controllers\\VoyagerUserController', 'generate_permissions' => 1, 'description' => '', ])->save(); } $dataType = $this->dataType('slug', 'menus'); if (!$dataType->exists) { $dataType->fill([ 'name' => 'menus', 'display_name_singular' => __('voyager::seeders.data_types.menu.singular'), 'display_name_plural' => __('voyager::seeders.data_types.menu.plural'), 'icon' => 'voyager-list', 'model_name' => 'TCG\\Voyager\\Models\\Menu', 'controller' => '', 'generate_permissions' => 1, 'description' => '', ])->save(); } $dataType = $this->dataType('slug', 'roles'); if (!$dataType->exists) { $dataType->fill([ 'name' => 'roles', 'display_name_singular' => __('voyager::seeders.data_types.role.singular'), 'display_name_plural' => __('voyager::seeders.data_types.role.plural'), 'icon' => 'voyager-lock', 'model_name' => 'TCG\\Voyager\\Models\\Role', 'controller' => '', 'generate_permissions' => 1, 'description' => '', ])->save(); } } /** * [dataType description]. * * @param [type] $field [description] * @param [type] $for [description] * * @return [type] [description] */ protected function dataType($field, $for) { return DataType::firstOrNew([$field => $for]); } } ================================================ FILE: database/seeders/DatabaseSeeder.php ================================================ call(UserSeeder::class); $this->call(CharacterSeeder::class); $this->call(VoyagerDatabaseSeeder::class); Model::reguard(); } } ================================================ FILE: database/seeders/MenuItemsTableSeeder.php ================================================ firstOrFail(); $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.dashboard'), 'url' => '', 'route' => 'voyager.dashboard', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-boat', 'color' => null, 'parent_id' => null, 'order' => 1, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.media'), 'url' => '', 'route' => 'voyager.media.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-images', 'color' => null, 'parent_id' => null, 'order' => 5, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.users'), 'url' => '', 'route' => 'voyager.users.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-person', 'color' => null, 'parent_id' => null, 'order' => 3, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.roles'), 'url' => '', 'route' => 'voyager.roles.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-lock', 'color' => null, 'parent_id' => null, 'order' => 2, ])->save(); } $toolsMenuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.tools'), 'url' => '', ]); if (!$toolsMenuItem->exists) { $toolsMenuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-tools', 'color' => null, 'parent_id' => null, 'order' => 9, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.menu_builder'), 'url' => '', 'route' => 'voyager.menus.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-list', 'color' => null, 'parent_id' => $toolsMenuItem->id, 'order' => 10, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.database'), 'url' => '', 'route' => 'voyager.database.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-data', 'color' => null, 'parent_id' => $toolsMenuItem->id, 'order' => 11, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.compass'), 'url' => '', 'route' => 'voyager.compass.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-compass', 'color' => null, 'parent_id' => $toolsMenuItem->id, 'order' => 12, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.bread'), 'url' => '', 'route' => 'voyager.bread.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-bread', 'color' => null, 'parent_id' => $toolsMenuItem->id, 'order' => 13, ])->save(); } $menuItem = MenuItem::firstOrNew([ 'menu_id' => $menu->id, 'title' => __('voyager::seeders.menu_items.settings'), 'url' => '', 'route' => 'voyager.settings.index', ]); if (!$menuItem->exists) { $menuItem->fill([ 'target' => '_self', 'icon_class' => 'voyager-settings', 'color' => null, 'parent_id' => null, 'order' => 14, ])->save(); } } } ================================================ FILE: database/seeders/MenusTableSeeder.php ================================================ 'admin', ]); } } ================================================ FILE: database/seeders/PermissionRoleTableSeeder.php ================================================ firstOrFail(); $permissions = Permission::all(); $role->permissions()->sync( $permissions->pluck('id')->all() ); } } ================================================ FILE: database/seeders/PermissionsTableSeeder.php ================================================ $key, 'table_name' => null, ]); } Permission::generateFor('menus'); Permission::generateFor('roles'); Permission::generateFor('users'); Permission::generateFor('settings'); } } ================================================ FILE: database/seeders/RolesTableSeeder.php ================================================ 'admin']); if (!$role->exists) { $role->fill([ 'display_name' => __('voyager::seeders.roles.admin'), ])->save(); } $role = Role::firstOrNew(['name' => 'user']); if (!$role->exists) { $role->fill([ 'display_name' => __('voyager::seeders.roles.user'), ])->save(); } } } ================================================ FILE: database/seeders/SettingsTableSeeder.php ================================================ findSetting('site.title'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.site.title'), 'value' => __('voyager::seeders.settings.site.title'), 'details' => '', 'type' => 'text', 'order' => 1, 'group' => 'Site', ])->save(); } $setting = $this->findSetting('site.description'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.site.description'), 'value' => __('voyager::seeders.settings.site.description'), 'details' => '', 'type' => 'text', 'order' => 2, 'group' => 'Site', ])->save(); } $setting = $this->findSetting('site.logo'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.site.logo'), 'value' => '', 'details' => '', 'type' => 'image', 'order' => 3, 'group' => 'Site', ])->save(); } $setting = $this->findSetting('site.google_analytics_tracking_id'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.site.google_analytics_tracking_id'), 'value' => '', 'details' => '', 'type' => 'text', 'order' => 4, 'group' => 'Site', ])->save(); } $setting = $this->findSetting('admin.bg_image'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.admin.background_image'), 'value' => '', 'details' => '', 'type' => 'image', 'order' => 5, 'group' => 'Admin', ])->save(); } $setting = $this->findSetting('admin.title'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.admin.title'), 'value' => 'Voyager', 'details' => '', 'type' => 'text', 'order' => 1, 'group' => 'Admin', ])->save(); } $setting = $this->findSetting('admin.description'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.admin.description'), 'value' => __('voyager::seeders.settings.admin.description_value'), 'details' => '', 'type' => 'text', 'order' => 2, 'group' => 'Admin', ])->save(); } $setting = $this->findSetting('admin.loader'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.admin.loader'), 'value' => '', 'details' => '', 'type' => 'image', 'order' => 3, 'group' => 'Admin', ])->save(); } $setting = $this->findSetting('admin.icon_image'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.admin.icon_image'), 'value' => '', 'details' => '', 'type' => 'image', 'order' => 4, 'group' => 'Admin', ])->save(); } $setting = $this->findSetting('admin.google_analytics_client_id'); if (!$setting->exists) { $setting->fill([ 'display_name' => __('voyager::seeders.settings.admin.google_analytics_client_id'), 'value' => '', 'details' => '', 'type' => 'text', 'order' => 1, 'group' => 'Admin', ])->save(); } } /** * [setting description]. * * @param [type] $key [description] * * @return [type] [description] */ protected function findSetting($key) { return Setting::firstOrNew(['key' => $key]); } } ================================================ FILE: database/seeders/TranslationsTableSeeder.php ================================================ dataTypesTranslations(); $this->categoriesTranslations(); $this->pagesTranslations(); $this->menusTranslations(); } /** * Auto generate Categories Translations. * * @return void */ private function categoriesTranslations() { // Adding translations for 'categories' // $cat = Category::where('slug', 'category-1')->firstOrFail(); if ($cat->exists) { $this->trans('pt', $this->arr(['categories', 'slug'], $cat->id), 'categoria-1'); $this->trans('pt', $this->arr(['categories', 'name'], $cat->id), 'Categoria 1'); } $cat = Category::where('slug', 'category-2')->firstOrFail(); if ($cat->exists) { $this->trans('pt', $this->arr(['categories', 'slug'], $cat->id), 'categoria-2'); $this->trans('pt', $this->arr(['categories', 'name'], $cat->id), 'Categoria 2'); } } /** * Auto generate DataTypes Translations. * * @return void */ private function dataTypesTranslations() { // Adding translations for 'display_name_singular' // $_fld = 'display_name_singular'; $_tpl = ['data_types', $_fld]; $dtp = DataType::where($_fld, __('voyager::seeders.data_types.post.singular'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Post'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.page.singular'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Página'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.user.singular'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Utilizador'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.category.singular'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Categoria'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.menu.singular'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Menu'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.role.singular'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Função'); } // Adding translations for 'display_name_plural' // $_fld = 'display_name_plural'; $_tpl = ['data_types', $_fld]; $dtp = DataType::where($_fld, __('voyager::seeders.data_types.post.plural'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Posts'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.page.plural'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Páginas'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.user.plural'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Utilizadores'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.category.plural'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Categorias'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.menu.plural'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Menus'); } $dtp = DataType::where($_fld, __('voyager::seeders.data_types.role.plural'))->firstOrFail(); if ($dtp->exists) { $this->trans('pt', $this->arr($_tpl, $dtp->id), 'Funções'); } } /** * Auto generate Pages Translations. * * @return void */ private function pagesTranslations() { $page = Page::where('slug', 'hello-world')->firstOrFail(); if ($page->exists) { $_arr = $this->arr(['pages', 'title'], $page->id); $this->trans('pt', $_arr, 'Olá Mundo'); /** * For configuring additional languages use it e.g. * * ``` * $this->trans('es', $_arr, 'hola-mundo'); * $this->trans('de', $_arr, 'hallo-welt'); * ``` */ $_arr = $this->arr(['pages', 'slug'], $page->id); $this->trans('pt', $_arr, 'ola-mundo'); $_arr = $this->arr(['pages', 'body'], $page->id); $this->trans('pt', $_arr, '
Olá Mundo. Scallywag grog swab Cat o\'nine tails scuttle rigging hardtack cable nipper Yellow Jack. Handsomely spirits knave lad killick landlubber or just lubber deadlights chantey pinnace crack Jennys tea cup. Provost long clothes black spot Yellow Jack bilged on her anchor league lateen sail case shot lee tackle.
' ."\r\n".'Ballast spirits fluke topmast me quarterdeck schooner landlubber or just lubber gabion belaying pin. Pinnace stern galleon starboard warp carouser to go on account dance the hempen jig jolly boat measured fer yer chains. Man-of-war fire in the hole nipperkin handsomely doubloon barkadeer Brethren of the Coast gibbet driver squiffy.
'); } } /** * Auto generate Menus Translations. * * @return void */ private function menusTranslations() { $_tpl = ['menu_items', 'title']; $_item = $this->findMenuItem(__('voyager::seeders.menu_items.dashboard')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Painel de Controle'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.media')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Media'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.posts')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Publicações'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.users')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Utilizadores'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.categories')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Categorias'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.pages')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Páginas'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.roles')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Funções'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.tools')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Ferramentas'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.menu_builder')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Menus'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.database')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Base de dados'); } $_item = $this->findMenuItem(__('voyager::seeders.menu_items.settings')); if ($_item->exists) { $this->trans('pt', $this->arr($_tpl, $_item->id), 'Configurações'); } } private function findMenuItem($title) { return MenuItem::where('title', $title)->firstOrFail(); } private function arr($par, $id) { return [ 'table_name' => $par[0], 'column_name' => $par[1], 'foreign_key' => $id, ]; } private function trans($lang, $keys, $value) { $_t = Translation::firstOrNew(array_merge($keys, [ 'locale' => $lang, ])); if (!$_t->exists) { $_t->fill(array_merge( $keys, ['value' => $value] ))->save(); } } } ================================================ FILE: database/seeders/UserSeeder.php ================================================ count()) { return; } $users = [ [ 'name' => 'Misha Chekin', 'password' => Hash::make('1234'), 'email' => 'mchekin@gmail.com', ], ]; foreach ($users as $user) { User::query()->create($user); } } } ================================================ FILE: database/seeders/VoyagerDatabaseSeeder.php ================================================ seed(DataTypesTableSeeder::class); $this->seed(DataRowsTableSeeder::class); $this->seed(MenusTableSeeder::class); $this->seed(MenuItemsTableSeeder::class); $this->seed(RolesTableSeeder::class); $this->seed(PermissionsTableSeeder::class); $this->seed(PermissionRoleTableSeeder::class); $this->seed(SettingsTableSeeder::class); } } ================================================ FILE: database/seeders/VoyagerDummyDatabaseSeeder.php ================================================ seedersPath = database_path('seeders').'/'; $this->seed('CategoriesTableSeeder'); $this->seed('UsersTableSeeder'); $this->seed('PostsTableSeeder'); $this->seed('PagesTableSeeder'); $this->seed('TranslationsTableSeeder'); $this->seed('PermissionRoleTableSeeder'); } } ================================================ FILE: docker/cron/Dockerfile ================================================ FROM php:7.4-cli # Install system dependencies RUN apt-get update && apt-get install -y \ build-essential \ git \ curl \ libpng-dev \ libonig-dev \ libxml2-dev \ libzip-dev \ zip \ unzip \ cron # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Install PHP extensions RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip # Set working directory WORKDIR /var/www # Add crontab file in the cron directory ADD ./docker/cron/scheduler /etc/cron.d/scheduler # Give execution rights on the cron job RUN chmod 0644 /etc/cron.d/scheduler # Apply the cron jobs RUN crontab /etc/cron.d/scheduler # run cron CMD ["cron", "-f"] ================================================ FILE: docker/cron/scheduler ================================================ * * * * * /usr/local/bin/php /var/www/artisan schedule:run > /proc/1/fd/1 2>/proc/1/fd/2 ================================================ FILE: docker/mysql/my.cnf ================================================ [mysqld] general_log = 1 general_log_file = /var/lib/mysql/general.log ================================================ FILE: docker/nginx/conf.d/app.conf ================================================ server { listen 80; index index.php index.html; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /var/www/public; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } } ================================================ FILE: docker/php/Dockerfile ================================================ FROM php:7.4-fpm # Arguments defined in docker-compose.yml ARG user ARG uid # Install NodeJS RUN curl -sL https://deb.nodesource.com/setup_12.x | bash # Install system dependencies RUN apt-get update && apt-get upgrade -y && apt-get install -y \ build-essential \ git \ curl \ libpng-dev \ libonig-dev \ libxml2-dev \ libzip-dev \ zip \ unzip \ nodejs \ yarn # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Install PHP extensions RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip # Install xdebug RUN pecl install xdebug && docker-php-ext-enable xdebug # Get latest Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Create system user to run Composer and Artisan Commands RUN useradd -G www-data,root -u $uid -d /home/$user $user RUN mkdir -p /home/$user/.composer && \ chown -R $user:$user /home/$user # Set working directory WORKDIR /var/www USER $user ================================================ FILE: docker/php/application-init.sh ================================================ composer install php artisan key:generate php artisan migrate --seed php artisan storage:link php-fpm ================================================ FILE: docker/php/local.ini ================================================ upload_max_filesize=40M post_max_size=40M [xdebug] xdebug.idekey=PHPSTORM xdebug.mode = debug xdebug.client_host=host.docker.internal xdebug.client_port=9001 xdebug.log=/var/www/storage/logs/xdebug.log ================================================ FILE: docker-compose.yml ================================================ version: "3.7" services: #PHP Service app: build: args: user: user uid: 1000 context: ./ dockerfile: docker/php/Dockerfile image: rpg container_name: rpg-app restart: unless-stopped tty: true environment: SERVICE_NAME: app SERVICE_TAGS: dev PHP_IDE_CONFIG: serverName=RpgServer working_dir: /var/www command: /var/www/docker/php/application-init.sh volumes: - ./:/var/www - ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini networks: - rpg-app-network depends_on: - db #Cron tab service cron: build: context: . dockerfile: docker/cron/Dockerfile image: cron container_name: rpg-cron restart: unless-stopped tty: true environment: SERVICE_NAME: app SERVICE_TAGS: dev working_dir: /var/www volumes: - ./:/var/www networks: - rpg-app-network depends_on: - app #Nginx Service nginx: image: nginx:1.17-alpine container_name: rpg-nginx restart: unless-stopped tty: true ports: - "8080:80" - "443:443" volumes: - ./:/var/www - ./docker/nginx/conf.d/:/etc/nginx/conf.d/ networks: - rpg-app-network depends_on: - app #MySQL Service db: image: mysql:5.7.32 container_name: rpg-db restart: unless-stopped tty: true ports: - "3306:3306" environment: MYSQL_DATABASE: ${DB_DATABASE} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_USER: ${DB_USERNAME} SERVICE_TAGS: dev SERVICE_NAME: mysql volumes: - dbdata:/var/lib/mysql - ./docker/mysql/my.cnf:/etc/mysql/my.cnf networks: - rpg-app-network #Docker Networks networks: rpg-app-network: driver: bridge #Volumes volumes: dbdata: driver: local ================================================ FILE: docs/docker_environment.md ================================================ ## Docker Development Environment  ### Table of Contents 1. [Requirements](#requirments) 2. [Installation](#installation) 4. [Using Admin Dashboard](#usingadmindashboard) ### Requirements - [Docker & Docker Compose](https://www.docker.com/get-started) ### Installation - Clone the repo git clone https://github.com/mchekin/rpg.git game - Navigate to the project folder cd game - Create .env file from the .env.example file cp .env.docker.example .env On Windows: copy .env.docker.example .env - Bring the containers up docker-compose up -d - Install NPM packages docker-compose exec app npm install - Build frontend assets docker-compose exec app npm run dev - Navigate to [http://localhost:8080/](http://localhost/8080) ### Using Admin Dashboard [Voyager](https://laravelvoyager.com/) has been integrated into the project as an Admin Dashboard. To use the Admin: - Register a user in the application (You can skip this step if you already have a user). - Give the user the admin role by running: docker-compose exec app php artisan voyager:admin| Sell price | |
|---|---|
| Type | {{ itemToDisplay.type | underscoreToWhitespace | capitalize }} |
| Effects |
|
| Sell price | |
|---|---|
| Type | {{ itemToDisplay.type | underscoreToWhitespace | capitalize }} |
| Effects |
|
| Sell price | {{ itemToDisplay.price }} | |
|---|---|---|
| Store buy price | {{ toStoreBuyPrice(itemToDisplay) }} | |
| Type | {{ itemToDisplay.type | underscoreToWhitespace | capitalize }} | |
| Effects |
|
| Race | {{ $character->getRaceName() }} |
|---|---|
| Gender | {{ $character->gender }} |
| Level | {{ $level->getId() }} |
| XP |
|
| Reputation | {{ $character->reputation }} |
|---|---|
| Money | {{ $character->inventory->money }} |
| Battles Won | {{ $character->battles_won }} |
| Battles Lost | {{ $character->battles_lost }} |
{{ $location->description }}
{!! $message->content !!}