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 ![](https://raw.githubusercontent.com/mchekin/rpg/f19c452aefcbd028c7db521bd50d1cec5995b137/public/images/locations/Fortress-300px.png) ### 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 - Navigate to [http://localhost:8080/admin](http://localhost:8080/admin). - Log in with the user credentials (If not logged in automatically). Further information about using Voyager can be found on its [official website](https://laravelvoyager.com/). ================================================ FILE: docs/local_environment.md ================================================ ## Local Development Environment ![](https://raw.githubusercontent.com/mchekin/rpg/f19c452aefcbd028c7db521bd50d1cec5995b137/public/images/locations/Townhall-300px.png) ### Table of Contents 1. [Requirements](#requirments) 2. [Installation](#installation) 3. [Using Admin Dashboard](#usingadmindashboard) ### Requirements (for local development) - PHP 7.2.0 or Higher - [Git](https://git-scm.com/) - [Composer](https://getcomposer.org/) - [SQLite](https://www.sqlite.org/) - [NodeJS](https://nodejs.org/) ### 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.example .env On Windows: copy .env.example .env - Run composer install to import the dependencies and enable auto-loading composer install - Generate Laravel Application key php artisan key:generate - Create SQLite database file touch database/database.sqlite On Windows: copy NUL database\database.sqlite - Run Laravel database migrations and seeds php artisan migrate --seed - Compile frontend assets npm install & npm run dev - Create a symbolic link from "public/storage" to "storage/app/public" php artisan storage:link - Run PHP build-in development server on the host machine php artisan serve - Navigate to [http://localhost:8000/](http://localhost:8000/) - Enable Laravel Task Scheduling 1. Open the cron tab file crontab -e 2. Add the following line and save * * * * * php /artisan schedule:run >> /dev/null 2>&1 On Windows: Open the Terminal as Administrator, navigate to the project's root folder and run: schtasks /create /sc minute /mo 1 /tn "RPG SCHEDULER" /tr %cd%\scheduler.bat To disable the annoying command-line pop-up each time the task runs: 1. Open Windows "Run" dialog by pressing "Windows Key + r" 2. Enter type "Taskschd.msc" and press Enter. This will open the "Task Scheduler". 3. In Task Scheduler's "Active Tasks" section find the "RPG SCHEDULER" task and double-click it. 4. In the left "Actions" panel click "Properties". This will open "Properties" pop-up. 5. In the pop-up select the "Run whether user is logged in or not" and press Enter. You maybe asked for your Windows user's password to complete the process. To remove the scheduled task you can use schtasks /delete /tn "RPG SCHEDULER" /f ### 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: php artisan voyager:admin - Navigate to [http://localhost:8000/admin](http://localhost:8000/admin). - Log in with the user credentials (If not logged in automatically). Further information about using Voyager can be found on its [official website](https://laravelvoyager.com/). ================================================ FILE: hooks/hooks.json ================================================ {} ================================================ FILE: package.json ================================================ { "private": true, "scripts": { "dev": "npm run development", "development": "mix", "watch": "mix watch", "watch-poll": "mix watch -- --watch-options-poll=1000", "hot": "mix watch --hot", "prod": "npm run production", "production": "mix --production" }, "devDependencies": { "@fortawesome/fontawesome-free": "^5.15.1", "axios": "^0.21", "bootstrap": "^4.0.0", "jquery": "^3.2", "laravel-mix": "^6.0.6", "lodash": "^4.17.21", "popper.js": "^1.12", "postcss": "^8.1.14", "resolve-url-loader": "^2.3.1", "sass": "^1.20.1", "sass-loader": "^8.0.0", "vue": "^2.5.17", "vue-loader": "^15.9.6", "vue-template-compiler": "^2.6.10" } } ================================================ FILE: phpunit.xml ================================================ ./tests/Unit ./tests/Feature ./app ================================================ FILE: public/.htaccess ================================================ Options -MultiViews -Indexes RewriteEngine On # Handle Authorization Header RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1 [L,R=301] # Send Requests To Front Controller... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] ================================================ FILE: public/index.php ================================================ make(Kernel::class); $response = tap($kernel->handle( $request = Request::capture() ))->send(); $kernel->terminate($request, $response); ================================================ FILE: public/js/character-create.js ================================================ (function($) { var carouselRace = $(".carousel-race"); var carouselGender = $(".carousel-gender"); var carouselRaceControl = $('#race-carousel'); var carouselGenderControl = $('#gender-carousel'); // Enable Carousel Controls function changeRace() { var raceIdRegex = /^(?:\w+)-(?:\w+)-([0-9]+)$/; var raceIdString = $('.carousel-item.active', carouselRaceControl).attr('id'); var matches = raceIdRegex.exec(raceIdString); $('#race_id').val(matches[1]); } function changeGender() { var gender = $('.carousel-item.active', carouselGenderControl).attr('id'); $('.img-race',carouselRace).hide(); $('.img-'+gender, carouselRace).show(); $('#gender').val(gender); }; $(".left-race").click(function () { carouselRace.carousel("prev"); changeRace(); }); $(".right-race").click(function () { carouselRace.carousel("next"); changeRace(); }); $(".left-gender").click(function () { carouselGender.carousel("prev"); changeGender(); }); $(".right-gender").click(function () { carouselGender.carousel("next"); changeGender(); }); })(jQuery ); ================================================ FILE: public/js/character-update.js ================================================ (function ($) { $(".increment_attribute").click(function (event) { let attributeName = this.id; $('#attribute_input').val(attributeName); }); })(jQuery); ================================================ FILE: public/js/vcountdown.js ================================================ /*jslint browser: true*/ /*global define, module, exports*/ (function (root, factory) { "use strict"; if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.VCountdown = factory(); } }(this, function () { "use strict"; let VCountdown = function (options) { if (!this || !(this instanceof VCountdown)) { return new VCountdown(options); } if (!options) { options = {}; } if (!options.target) { throw 'Provide a target to count characters'; } this.target = document.querySelector(options.target); this.maxChars = options.maxChars || 140; this.countdown(); }; VCountdown.prototype = { hasClass: function (el, name) { return new RegExp('(\\s|^)' + name + '(\\s|$)').test(el.className); }, addClass: function (el, name) { if (!this.hasClass(el, name)) { el.className += (el.className ? ' ' : '') + name; } }, removeClass: function (el, name) { if (this.hasClass(el, name)) { el.className = el.className.replace(new RegExp('(\\s|^)' + name + '(\\s|$)'), ' ').replace(/^\s+|\s+$/g, ''); } }, createEls: function (name, props) { let el = document.createElement(name), p; for (p in props) { if (props.hasOwnProperty(p)) { el[p] = props[p]; } } return el; }, insertAfter: function (referenceNode, newNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); }, update: function () { let target = this.target, currentCount = target.value.length, remaining = this.maxChars - currentCount; if (remaining > 10) { this.removeClass(target.nextElementSibling, 'warn'); } else { this.addClass(target.nextElementSibling, 'warn'); } target.nextElementSibling.innerHTML = remaining + ' remaining'; }, setMaxChars: function () { this.target.setAttribute('maxlength', this.maxChars); }, charsLen: function () { let element = this.createEls('div', {className: 'chars-length'}); element.innerHTML = this.maxChars; this.insertAfter(this.target, element); this.update(); }, countdown: function () { this.setMaxChars(); this.charsLen(); this.target.addEventListener('keyup', this.update.bind(this), false); } }; return VCountdown; })); ================================================ FILE: public/robots.txt ================================================ User-agent: * Disallow: ================================================ FILE: public/web.config ================================================ ================================================ FILE: readme.md ================================================ ## Online Role Playing Game ![](https://raw.githubusercontent.com/mchekin/rpg/f19c452aefcbd028c7db521bd50d1cec5995b137/public/images/locations/Blacksmith-300px.png) ![](https://travis-ci.org/mchekin/rpg.svg) ### Table of Contents 1. [Running in development environment](#runningindevelopmentenvironment) 2. [License](#license) ### Running in development environment #### Docker (recommended) Go to >>> [Guide](docs/docker_environment.md) #### Local Go to >>> [Guide](docs/local_environment.md) ### License Open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) ================================================ FILE: resources/js/app.js ================================================ require('./bootstrap'); window.Vue = require('vue').default; Vue.filter('underscoreToWhitespace', function (value) { return value.split('_').join(' '); }); Vue.filter('capitalize', function (value) { return value.charAt(0).toUpperCase() + value.slice(1) }); Vue.filter('plusForPositiveNumber', function (value) { return (Number.isInteger(value) && value > 0) ? '+' + value : value; }); Vue.component( 'popup-modal', require('./components/PopupModal.vue').default ); Vue.component( 'inventory-management', require('./components/InventoryManagement.vue').default ); Vue.component( 'store-management', require('./components/StoreManagement.vue').default ); Vue.component( 'store-trade', require('./components/StoreTrade.vue').default ); Vue.component( 'flash-messages', require('./components/FlashMessages.vue').default ); const app = new Vue({ el: '#app', }); ================================================ FILE: resources/js/bootstrap.js ================================================ window._ = require('lodash'); /** * We'll load jQuery and the Bootstrap jQuery plugin which provides support * for JavaScript based Bootstrap features such as modals and tabs. This * code may be modified to fit the specific needs of your application. */ try { window.Popper = require('popper.js').default; window.$ = window.jQuery = require('jquery'); require('bootstrap'); } catch (e) {} /** * We'll load the axios HTTP library which allows us to easily issue requests * to our Laravel back-end. This library automatically handles sending the * CSRF token as a header based on the value of the "XSRF" token cookie. */ window.axios = require('axios'); window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; /** * Next we will register the CSRF Token as a common header with Axios so that * all outgoing HTTP requests automatically have it attached. This is just * a simple convenience so we don't have to attach every token manually. */ let token = document.head.querySelector('meta[name="csrf-token"]'); if (token) { window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; } else { console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); } /** * Echo exposes an expressive API for subscribing to channels and listening * for events that are broadcast by Laravel. Echo and event broadcasting * allows your team to easily build robust real-time web applications. */ // import Echo from 'laravel-echo'; // window.Pusher = require('pusher-js'); // window.Echo = new Echo({ // broadcaster: 'pusher', // key: process.env.MIX_PUSHER_APP_KEY, // cluster: process.env.MIX_PUSHER_APP_CLUSTER, // forceTLS: true // }); ================================================ FILE: resources/js/components/FlashMessages.vue ================================================ ================================================ FILE: resources/js/components/InventoryManagement.vue ================================================ ================================================ FILE: resources/js/components/PopupModal.vue ================================================ ================================================ FILE: resources/js/components/StoreManagement.vue ================================================ ================================================ FILE: resources/js/components/StoreTrade.vue ================================================ ================================================ FILE: resources/lang/en/auth.php ================================================ 'These credentials do not match our records.', 'password' => 'The provided password is incorrect.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', ]; ================================================ FILE: resources/lang/en/pagination.php ================================================ '« Previous', 'next' => 'Next »', ]; ================================================ FILE: resources/lang/en/passwords.php ================================================ 'Your password has been reset!', 'sent' => 'We have emailed your password reset link!', 'throttled' => 'Please wait before retrying.', 'token' => 'This password reset token is invalid.', 'user' => "We can't find a user with that email address.", ]; ================================================ FILE: resources/lang/en/validation.php ================================================ 'The :attribute must be accepted.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', 'alpha' => 'The :attribute must only contain letters.', 'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.', 'alpha_num' => 'The :attribute must only contain letters and numbers.', 'array' => 'The :attribute must be an array.', 'before' => 'The :attribute must be a date before :date.', 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', 'between' => [ 'numeric' => 'The :attribute must be between :min and :max.', 'file' => 'The :attribute must be between :min and :max kilobytes.', 'string' => 'The :attribute must be between :min and :max characters.', 'array' => 'The :attribute must have between :min and :max items.', ], 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', 'digits_between' => 'The :attribute must be between :min and :max digits.', 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'email' => 'The :attribute must be a valid email address.', 'ends_with' => 'The :attribute must end with one of the following: :values.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', 'gt' => [ 'numeric' => 'The :attribute must be greater than :value.', 'file' => 'The :attribute must be greater than :value kilobytes.', 'string' => 'The :attribute must be greater than :value characters.', 'array' => 'The :attribute must have more than :value items.', ], 'gte' => [ 'numeric' => 'The :attribute must be greater than or equal :value.', 'file' => 'The :attribute must be greater than or equal :value kilobytes.', 'string' => 'The :attribute must be greater than or equal :value characters.', 'array' => 'The :attribute must have :value items or more.', ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', 'integer' => 'The :attribute must be an integer.', 'ip' => 'The :attribute must be a valid IP address.', 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', 'lt' => [ 'numeric' => 'The :attribute must be less than :value.', 'file' => 'The :attribute must be less than :value kilobytes.', 'string' => 'The :attribute must be less than :value characters.', 'array' => 'The :attribute must have less than :value items.', ], 'lte' => [ 'numeric' => 'The :attribute must be less than or equal :value.', 'file' => 'The :attribute must be less than or equal :value kilobytes.', 'string' => 'The :attribute must be less than or equal :value characters.', 'array' => 'The :attribute must not have more than :value items.', ], 'max' => [ 'numeric' => 'The :attribute must not be greater than :max.', 'file' => 'The :attribute must not be greater than :max kilobytes.', 'string' => 'The :attribute must not be greater than :max characters.', 'array' => 'The :attribute must not have more than :max items.', ], 'mimes' => 'The :attribute must be a file of type: :values.', 'mimetypes' => 'The :attribute must be a file of type: :values.', 'min' => [ 'numeric' => 'The :attribute must be at least :min.', 'file' => 'The :attribute must be at least :min kilobytes.', 'string' => 'The :attribute must be at least :min characters.', 'array' => 'The :attribute must have at least :min items.', ], 'multiple_of' => 'The :attribute must be a multiple of :value.', 'not_in' => 'The selected :attribute is invalid.', 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'password' => 'The password is incorrect.', 'present' => 'The :attribute field must be present.', 'regex' => 'The :attribute format is invalid.', 'required' => 'The :attribute field is required.', 'required_if' => 'The :attribute field is required when :other is :value.', 'required_unless' => 'The :attribute field is required unless :other is in :values.', 'required_with' => 'The :attribute field is required when :values is present.', 'required_with_all' => 'The :attribute field is required when :values are present.', 'required_without' => 'The :attribute field is required when :values is not present.', 'required_without_all' => 'The :attribute field is required when none of :values are present.', 'prohibited' => 'The :attribute field is prohibited.', 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', 'same' => 'The :attribute and :other must match.', 'size' => [ 'numeric' => 'The :attribute must be :size.', 'file' => 'The :attribute must be :size kilobytes.', 'string' => 'The :attribute must be :size characters.', 'array' => 'The :attribute must contain :size items.', ], 'starts_with' => 'The :attribute must start with one of the following: :values.', 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', 'uuid' => 'The :attribute must be a valid UUID.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap our attribute placeholder | with something more reader friendly such as "E-Mail Address" instead | of "email". This simply helps us make our message more expressive. | */ 'attributes' => [], ]; ================================================ FILE: resources/sass/_variables.scss ================================================ // Body $body-bg: #f8fafc; // Typography $font-family-sans-serif: 'Nunito', sans-serif; $font-size-base: 0.9rem; $line-height-base: 1.6; // Colors $blue: #3490dc; $indigo: #6574cd; $purple: #9561e2; $pink: #f66d9b; $red: #e3342f; $orange: #f6993f; $yellow: #ffed4a; $green: #38c172; $teal: #4dc0b5; $cyan: #6cb2eb; ================================================ FILE: resources/sass/app.scss ================================================ // Fonts @import url('https://fonts.googleapis.com/css?family=Nunito'); // Variables @import 'variables'; // Bootstrap @import '~bootstrap/scss/bootstrap'; // Bootstrap @import '~@fortawesome/fontawesome-free/css/all.min.css'; // Custom css @import "custom.scss"; ================================================ FILE: resources/sass/custom.scss ================================================ /* Main title */ .title { font-size: 50px; } /* Carousel */ .carousel-control { background-image: none !important; filter: none !important; color: black !important; } .carousel-content { display: block; text-align: center; } /* Character attributes table */ caption.caption-top { caption-side: top !important; } .table-attributes { /*width: 70%;*/ /*margin-left: 15%;*/ font-size: 1.8rem; } .table-attributes td.circle a { border-radius: 25px; background-color: #337ab7; /*font-weight: bold;*/ color: white; } /* Images */ .img-race { width: 80% !important; } .active a { color: white !important; } /* Messages */ .message-list-container { border: 2px solid #dedede; background-color: #f1f1f1; border-radius: 5px; padding: 10px; margin: 10px 0; } /* Darker chat container */ .message-list-darker { border-color: #ccc; background-color: #ddd; } /* Clear floats */ .message-list-container::after { content: ""; clear: both; display: table; } /* Message links */ .message-list-container a { color: black !important; } .message-list-container a:active { color: red !important; } /* Message avatar */ .nav-avatar { overflow: hidden; height: 50px; } .profile-picture-wrapper { height: 350px; max-width: 80% !important; margin-left: auto !important; margin-right: auto !important; } img.profile-picture { display: block !important; max-height: 100% !important; max-width: 100% !important; margin: auto !important; } img.profile-picture-msg { max-width: 90px; width: 100%; } img.profile-picture-nav { max-width: 80px; width: 100%; height: 100%; } /* Message Textarea Counter */ .chars-length { display: block; font-size: 12px; padding: 5px 20px; text-align: right; position: relative; width: 100%; } .warn { background-color: firebrick; color: #fff; } /** Online/Offline status dots */ .dot { height: 10px; width: 10px; background-color: #bbb; border-radius: 50%; display: inline-block; } .dot-online { background-color: lightgreen; } .dot-offline { background-color: lightgray; } .dot-npc { opacity: 0.0; filter: alpha(opacity=0); /* For IE8 and earlier */ } /** General */ .badge-lightpink { color: #fff; background-color: lightpink; } .badge-lightskyblue { color: #fff; background-color: lightskyblue; } .text-wrap { word-wrap: break-word; } /** Location Navigator */ .location-navigator .row { /*border: 1px solid red;*/ height: 50px; } /** Specific message display */ .messages { list-style: none; padding: 20px 10px 0 10px; margin: 0; } .messages .message { clear: both; overflow: hidden; margin-bottom: 20px; transition: all 0.5s linear; } .messages .message.left .text_wrapper { background-color: #f7ffd6; margin-left: 20px; } .messages .message.left .text_wrapper::after, .messages .message.left .text_wrapper::before { right: 100%; border-right-color: #f7ffd6; } .messages .message.right .text_wrapper { background-color: #c7eafc; margin-right: 20px; float: right; } .messages .message.right .text_wrapper::after, .messages .message.right .text_wrapper::before { left: 100%; border-left-color: #c7eafc; } .messages .message .text_wrapper { padding: 20px; border-radius: 6px; width: calc(100% - 85px); min-width: 100px; position: relative; } .messages .message .text_wrapper::after, .messages .message .text_wrapper:before { top: 18px; border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; } .messages .message .text_wrapper::after { border-width: 13px; margin-top: 0; } .messages .message .text_wrapper::before { border-width: 15px; margin-top: -2px; } /** Character Equipment **/ .equipment-item { height: 150px; border: 1px solid gray; } .inventory-item { height: 110px; border: 1px solid gray; width: 20%; } .inventory-item.equipped { background-color: #f7ffd6; } .inventory-item img, .equipment-item img { display: block !important; max-height: 90% !important; max-width: 100% !important; width: 90% !important; margin-left: auto !important; margin-right: auto !important; margin-top: 5px !important; } .btn-link-thin { margin:0 !important; padding:0 !important; width:100% !important; font-weight: 400; color: #007bff; background-color: transparent; } .btn-xxs { padding: .25rem .4rem; font-size: .700rem; line-height: .3; border-radius: .2rem; } ================================================ FILE: resources/views/auth/login.blade.php ================================================ @extends('base') @section('body')
Login
{{ csrf_field() }}
@if ($errors->has('email')) {{ $errors->first('email') }} @endif
@if ($errors->has('password')) {{ $errors->first('password') }} @endif
@endsection ================================================ FILE: resources/views/auth/passwords/email.blade.php ================================================ @extends('base') @section('body')
Reset Password
{{ csrf_field() }}
@if ($errors->has('email')) {{ $errors->first('email') }} @endif
@endsection ================================================ FILE: resources/views/auth/passwords/reset.blade.php ================================================ @extends('base') @section('body')
Reset Password
{{ csrf_field() }}
@if ($errors->has('email')) {{ $errors->first('email') }} @endif
@if ($errors->has('password')) {{ $errors->first('password') }} @endif
@if ($errors->has('password_confirmation')) {{ $errors->first('password_confirmation') }} @endif
@endsection ================================================ FILE: resources/views/auth/register.blade.php ================================================ @extends('base') @section('body')
Register
{{ csrf_field() }}
@if ($errors->has('name')) {{ $errors->first('name') }} @endif
@if ($errors->has('email')) {{ $errors->first('email') }} @endif
@if ($errors->has('password')) {{ $errors->first('password') }} @endif
@endsection ================================================ FILE: resources/views/base.blade.php ================================================ @section('head') {{----}} @show
@include('partials.navbar') @include('partials.flash-messages') @yield('body')
@section('footer') @show ================================================ FILE: resources/views/battle/partials/no-battles.blade.php ================================================
No battles.
================================================ FILE: resources/views/battle/show.blade.php ================================================ @extends("base") @section("head") {{ $battle->location->name }} @parent @stop @section("body")

Participants



Back

Battle log:


    @foreach($battle->rounds as $index => $round)
  • Round {{$index + 1}}

      @foreach($round->turns as $index => $turn) @switch($turn->result_type) @case('miss')
    • {{ $turn->executor->name }} was unable to hit {{ $turn->target->name }}.
    • @break @case('hit')
    • {{ $turn->executor->name }} did {{ $turn->damageDone }} damage to {{ $turn->target->name }}. @if($turn->damageAbsorbed)
      {{ $turn->damageAbsorbed }} damage was absorbed by {{ $turn->target->name }}'s armor. @endif
    • @break @case('critical_hit')
    • {{ $turn->executor->name }} did {{ $turn->damageDone }} critical damage to {{ $turn->target->name }}. @if($turn->damageAbsorbed)
      {{ $turn->damageAbsorbed }} damage was absorbed by {{ $turn->target->name }}'s armor. @endif
    • @break @default Something went wrong ... @endswitch @endforeach
  • @endforeach
@stop ================================================ FILE: resources/views/character/battle/index.blade.php ================================================ @extends("base") @section("head") {{ $character->name }} battles @parent @stop @section("body") @stop ================================================ FILE: resources/views/character/create.blade.php ================================================ @extends("base") @section("head") Create Character @parent @stop @section("body")
{!! csrf_field() !!}

Create game character

@stop @section("footer") @parent @stop ================================================ FILE: resources/views/character/inventory/index.blade.php ================================================ @extends('base') @section('head') Inventory @parent @stop @section('body')
@include('character.partials.general', compact('character', 'level')) @include('character.partials.attributes', compact('character')) @include('character.partials.statistics', compact('character'))
@stop @section('footer') @parent @stop ================================================ FILE: resources/views/character/message/index.blade.php ================================================ @extends("base") @section("head") Conversation with {{$character->name}} @parent @stop @section("body")
{!! csrf_field() !!}
    @foreach ($messages as $message) @if($message->from_id === $currentCharacter->id) @include('message.partials.my-message', compact('message')) @else @include('message.partials.others-message', compact('message')) @endif @endforeach {{ $messages->links() }}
@stop @section("footer") @parent @stop ================================================ FILE: resources/views/character/partials/actions.blade.php ================================================
{!! csrf_field() !!}
@if($character->isYou()) Inventory Store @else
@if($character->isPlayerCharacter()) message @endif trade
@endif
================================================ FILE: resources/views/character/partials/attributes.blade.php ================================================ isYou() && $character->available_attribute_points); ?> @if($hasFreePoints)
{{ method_field('PUT') }} {!! csrf_field() !!} @endif @component('components.increment_attribute_button', compact('hasFreePoints')) {{ 'strength' }} @endcomponent @component('components.increment_attribute_button', compact('hasFreePoints')) {{ 'agility' }} @endcomponent @component('components.increment_attribute_button', compact('hasFreePoints')) {{ 'constitution' }} @endcomponent @component('components.increment_attribute_button', compact('hasFreePoints')) {{ 'intelligence' }} @endcomponent @component('components.increment_attribute_button', compact('hasFreePoints')) {{ 'charisma' }} @endcomponent @if($hasFreePoints) @endif
Attributes
Strength {{ $character->strength }}
Agility {{ $character->agility }}
Constitution {{ $character->constitution }}
Intelligence {{ $character->intelligence }}
Charisma {{ $character->charisma }}
Available points {{ $character->available_attribute_points }}
@if($hasFreePoints)
@endif ================================================ FILE: resources/views/character/partials/character-display.blade.php ================================================ @php /** @var \App\Models\Character $character */ $hpPercent = ($character->getHitPoints() / $character->getTotalHitPoints()) * 100; @endphp
{{ $character->getHitPoints() }} / {{ $character->getTotalHitPoints() }}
@if($character->isYou())
{!! csrf_field() !!}
@if($character->hasProfilePicture())
@php /** @var \App\Models\Character $character */ $profile_picture = $character->getProfilePicture(); @endphp
{{ method_field('DELETE') }} {!! csrf_field() !!}
@endif @endif ================================================ FILE: resources/views/character/partials/equipment-item-mutable.blade.php ================================================ @if($item) @endif ================================================ FILE: resources/views/character/partials/equipment-item.blade.php ================================================ @if($item) @endif ================================================ FILE: resources/views/character/partials/equipment-mutable.blade.php ================================================ @php /** @var \App\Models\Character $character */ /** @var \App\Models\Item $item */ @endphp
{!! csrf_field() !!}
@php $item = $character->getHeadGearItem(); @endphp Head gear @include('character.partials.equipment-item-mutable', compact('item'))
@php $item = $character->getBodyArmorItem(); @endphp Body armor @include('character.partials.equipment-item-mutable', compact('item'))
@php $item = $character->getMainHandItem(); @endphp Main hand @include('character.partials.equipment-item-mutable', compact('item'))
@php $item = $character->getOffHandItem(); @endphp Off hand @include('character.partials.equipment-item-mutable', compact('item'))
================================================ FILE: resources/views/character/partials/equipment.blade.php ================================================ @php /** @var \App\Models\Character $character */ /** @var \App\Models\Item $item */ @endphp
@php $item = $character->getHeadGearItem() @endphp Head gear @include('character.partials.equipment-item', compact('item'))
@php $item = $character->getBodyArmorItem() @endphp Body armor @include('character.partials.equipment-item', compact('item'))
@php $item = $character->getMainHandItem() @endphp Main hand @include('character.partials.equipment-item', compact('item'))
@php $item = $character->getOffHandItem() @endphp Off hand @include('character.partials.equipment-item', compact('item'))
================================================ FILE: resources/views/character/partials/general.blade.php ================================================
General
Race {{ $character->getRaceName() }}
Gender {{ $character->gender }}
Level {{ $level->getId() }}
XP
{{ $character->xp }} / {{ $level->getNextXpThreshold() }}
================================================ FILE: resources/views/character/partials/inventory.blade.php ================================================ @php /** @var \App\Models\Character $character */ /** @var \App\Models\Item $item */ $items = $character->inventory->items; @endphp
{!! csrf_field() !!}
@foreach(range(0, App\Modules\Equipment\Domain\Inventory::NUMBER_OF_SLOTS) as $slotNumber) @php $item = $items->where('pivot.inventory_slot_number', $slotNumber)->first(); $isHighlighted = isset($item) && $item->isEquipped() ? 'equipped' : ''; @endphp
@if($item) @if($item->isEquipped()) @else @endif @endif
@endforeach
================================================ FILE: resources/views/character/partials/statistics.blade.php ================================================
Statistics
Reputation {{ $character->reputation }}
Money {{ $character->inventory->money }}
Battles Won {{ $character->battles_won }}
Battles Lost {{ $character->battles_lost }}
================================================ FILE: resources/views/character/show.blade.php ================================================ @extends('base') @section('head') {{ $character->getName() }} (Level: {{ $character->getLevelNumber() }}) @parent @stop @section('body')
{{ $character->getName() }}
@include('character.partials.character-display', compact('character')) @include('character.partials.equipment', compact('character')) @include('character.partials.actions', compact('character'))
@include('character.partials.general', compact('character')) @include('character.partials.attributes', compact('character')) @include('character.partials.statistics', compact('character'))
@stop @section('footer') @parent @stop ================================================ FILE: resources/views/components/increment_attribute_button.blade.php ================================================ @if($hasFreePoints) @endif ================================================ FILE: resources/views/components/short_character_description.blade.php ================================================ {{ $character->name }} ({{ $character->getRaceName() }}) ================================================ FILE: resources/views/emails/password.blade.php ================================================ Click here to reset your password: {{ url('password/reset/'.$token) }} ================================================ FILE: resources/views/errors/503.blade.php ================================================ @extends("base") @section("head") Be right back. @parent @stop @section("body")
Be right back.
@stop ================================================ FILE: resources/views/location/partials/list-character.blade.php ================================================ isNPC()) { $class = $character->isYou() ? 'active' : ''; } ?>
  • @if($character->isNPC()) @elseif($character->isOnline()) @else @endif @if($character->gender === 'male') @else @endif @component('components.short_character_description', compact('character')) @endcomponent @if(!$character->isYou()) @if(!$character->isNPC()) message @endif @if($character->isMerchant()) trade @endif @endif
  • ================================================ FILE: resources/views/location/partials/navigator.blade.php ================================================ @php /** @var \App\Models\Character $character */ $character = Auth::user()->character; @endphp
    {!! csrf_field() !!}
    @if(!is_null($adjacent = $location->adjacent('north')))
    @endif
    @if(!is_null($adjacent = $location->adjacent('west')))
    @endif
    @if(!is_null($adjacent = $location->adjacent('east')))
    @endif
    @if(!is_null($adjacent = $location->adjacent('south')))
    @endif
    ================================================ FILE: resources/views/location/show.blade.php ================================================ @extends('base') @section('head') {{ $location->name }} @parent @stop @section('body')
    {{ $location->name }}

    {{ $location->description }}

    @include('location.partials.navigator', compact('location'))
    Local characters:

    {!! csrf_field() !!}
      {!! csrf_field() !!} @foreach($location->characters as $character) @include('location.partials.list-character', compact('character')) @endforeach
    @stop ================================================ FILE: resources/views/message/index.blade.php ================================================ @extends("base") @section("head") Inbox @parent @stop @section("body")
      @forelse ($messages as $message) @php list($otherCharacter, $side) = ($message->sender->id === $currentCharacter->id) ? [$message->recipient, 'left'] : [$message->sender, 'right']; @endphp @include('message.partials.conversation', compact( 'message', 'currentCharacter', 'otherCharacter', 'side' )) @empty @include('message.partials.no-messages') @endforelse {{ $messages->links() }}
    @stop ================================================ FILE: resources/views/message/partials/conversation-card.blade.php ================================================
    {{ $character->name }}
    Avatar
    {{ $message->created_at->diffForHumans() }}
    ================================================ FILE: resources/views/message/partials/conversation-message.blade.php ================================================

    {!! $message->content !!}

    ================================================ FILE: resources/views/message/partials/conversation.blade.php ================================================
    @include('message.partials.conversation-card', ['character' => $currentCharacter, 'message' => $message])
    @include('message.partials.conversation-message', ['character' => $otherCharacter->id, 'message' => $message])
    @include('message.partials.conversation-card', ['character' => $otherCharacter, 'message' => $message])
    ================================================ FILE: resources/views/message/partials/my-message.blade.php ================================================
    @include('message.partials.conversation-card', ['character' => $message->sender, 'message' => $message])

    {!! $message->content !!}

    ================================================ FILE: resources/views/message/partials/no-messages.blade.php ================================================
    No messages.
    ================================================ FILE: resources/views/message/partials/others-message.blade.php ================================================

    {!! $message->content !!}

    @include('message.partials.conversation-card', ['character' => $message->sender, 'message' => $message])
    ================================================ FILE: resources/views/pages/index.blade.php ================================================ @extends("base") @section('head') {{ config('app.name') }} @parent @stop @section("body")

    {{ config('app.name') }}

    @stop ================================================ FILE: resources/views/partials/flash-messages.blade.php ================================================ @php /** @var \Illuminate\Support\ViewErrorBag $errors */ $messages = []; foreach ($errors->all() as $error) { $messages[] = [ 'text' => $error, 'type' => 'error', ]; } if (session('status')) { $messages[] = [ 'text' => session('status'), 'type' => 'success', ]; } @endphp ================================================ FILE: resources/views/partials/navbar.blade.php ================================================ ================================================ FILE: resources/views/trade/own_store/index.blade.php ================================================ @extends('base') @section('head') Store @parent @stop @section('body') @stop @section('footer') @parent @stop ================================================ FILE: resources/views/trade/store/index.blade.php ================================================ @extends('base') @section('head') Store @parent @stop @section('body')
    @stop @section('footer') @parent @stop ================================================ FILE: resources/views/vendor/.gitkeep ================================================ ================================================ FILE: resources/views/vendor/pagination/bootstrap-4.blade.php ================================================ @if ($paginator->hasPages())
      {{-- Previous Page Link --}} @if ($paginator->onFirstPage())
    • «
    • @else
    • @endif {{-- Pagination Elements --}} @foreach ($elements as $element) {{-- "Three Dots" Separator --}} @if (is_string($element))
    • {{ $element }}
    • @endif {{-- Array Of Links --}} @if (is_array($element)) @foreach ($element as $page => $url) @if ($page == $paginator->currentPage())
    • {{ $page }}
    • @else
    • {{ $page }}
    • @endif @endforeach @endif @endforeach {{-- Next Page Link --}} @if ($paginator->hasMorePages())
    • @else
    • »
    • @endif
    @endif ================================================ FILE: resources/views/vendor/pagination/default.blade.php ================================================ @if ($paginator->hasPages())
      {{-- Previous Page Link --}} @if ($paginator->onFirstPage())
    • «
    • @else
    • @endif {{-- Pagination Elements --}} @foreach ($elements as $element) {{-- "Three Dots" Separator --}} @if (is_string($element))
    • {{ $element }}
    • @endif {{-- Array Of Links --}} @if (is_array($element)) @foreach ($element as $page => $url) @if ($page == $paginator->currentPage())
    • {{ $page }}
    • @else
    • {{ $page }}
    • @endif @endforeach @endif @endforeach {{-- Next Page Link --}} @if ($paginator->hasMorePages())
    • @else
    • »
    • @endif
    @endif ================================================ FILE: resources/views/vendor/pagination/semantic-ui.blade.php ================================================ @if ($paginator->hasPages()) @endif ================================================ FILE: resources/views/vendor/pagination/simple-bootstrap-4.blade.php ================================================ @if ($paginator->hasPages()) @endif ================================================ FILE: resources/views/vendor/pagination/simple-default.blade.php ================================================ @if ($paginator->hasPages()) @endif ================================================ FILE: routes/api.php ================================================ get('/user', function (Request $request) { return $request->user(); }); ================================================ FILE: routes/channels.php ================================================ id === (int) $id; }); ================================================ FILE: routes/console.php ================================================ comment(Inspiring::quote()); })->describe('Display an inspiring quote'); ================================================ FILE: routes/web.php ================================================ name('character.move'); Route::post('/character/{character}/attack', 'CharacterController@attack')->name('character.attack'); Route::post('/inventory/item/{item}/equip', 'InventoryController@equipItem')->name('inventory.item.equip'); Route::post('/inventory/item/{item}/un-equip', 'InventoryController@unEquipItem')->name('inventory.item.un-equip'); // Simple routes... Route::group(['middleware' => 'guest'], static function () { Route::get('/', static function () { return view('pages.index'); })->name('index'); }); Route::group(['middleware' => ['auth', 'has.character']], static function () { Route::get('/home', static function () { $location = Auth::user()->character->location; return redirect()->route('location.show', compact('location')); })->name('home'); }); Auth::routes(); // Route resources... Route::resource('inventory', 'InventoryController')->only('index'); Route::resource('store', 'OwnStoreController')->only('index'); Route::resource('character', 'CharacterController')->only('create', 'store', 'show', 'update'); Route::resource('location', 'LocationController')->only(['show']); Route::resource('battle', 'BattleController')->only(['show']); Route::resource('message', 'MessageController')->only(['index']); Route::resource('character.message', 'CharacterMessageController')->only(['index', 'store']); Route::resource('character.store', 'CharacterStoreController')->only(['index']); Route::resource('character.profile-picture', 'ProfilePictureController')->only(['store', 'destroy']); Route::resource('character.battle', 'CharacterBattleController')->only(['index']); Route::group(['prefix' => 'admin'], static function () { Voyager::routes(); }); Route::middleware(['auth', 'has.character'])->namespace('Api')->prefix('api')->group(static function () { // Manage inventory Route::post('/inventory/item/{item}/equip', 'ManageInventoryController@equipItem')->name('inventory.item.equip'); Route::post('/inventory/item/{item}/un-equip', 'ManageInventoryController@unEquipItem')->name('inventory.item.un-equip'); // Manage store Route::post('/inventory/item/{item}/move-to-store', 'ManageStoreController@moveItemToStore')->name('inventory.item.move-to-store'); Route::post('/store/item/{item}/change-price', 'ManageStoreController@changeItemPrice')->name('store.item.change-price'); Route::post('/store/item/{item}/move-to-inventory', 'ManageStoreController@moveItemToInventory')->name('store.item.move-to-inventory'); Route::post('/inventory/money/move-to-store', 'ManageStoreController@moveMoneyToStore')->name('inventory.money.move-to-store'); Route::post('/store/money/move-to-inventory', 'ManageStoreController@moveMoneyToInventory')->name('store.money.move-to-inventory'); // Trade Route::post('/store/{store}/item/{item}/buy', 'TradeController@buyItem')->name('store.item.buy'); Route::post('/store/{store}/item/{item}/sell', 'TradeController@sellItem')->name('store.item.sell'); }); ================================================ FILE: scheduler.bat ================================================ cd %~dp0% php artisan schedule:run 1>> NUL 2>&1 ================================================ FILE: server.php ================================================ */ $uri = urldecode( parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ); // This file allows us to emulate Apache's "mod_rewrite" functionality from the // built-in PHP web server. This provides a convenient way to test a Laravel // application without having installed a "real" web server software here. if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { return false; } require_once __DIR__.'/public/index.php'; ================================================ FILE: storage/app/public/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/debugbar/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/framework/.gitignore ================================================ compiled.php config.php down events.scanned.php maintenance.php routes.php routes.scanned.php schedule-* services.json ================================================ FILE: storage/framework/cache/.gitignore ================================================ * !data/ !.gitignore ================================================ FILE: storage/framework/sessions/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/framework/testing/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/framework/views/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/logs/.gitignore ================================================ * !.gitignore ================================================ FILE: tests/Browser/ExampleTest.php ================================================ browse(function (Browser $browser) { $browser->visit('/') ->assertSee(config('app.name')); }); } } ================================================ FILE: tests/Browser/Pages/HomePage.php ================================================ '#selector', ]; } } ================================================ FILE: tests/Browser/Pages/Page.php ================================================ '#selector', ]; } } ================================================ FILE: tests/Browser/console/.gitignore ================================================ * !.gitignore ================================================ FILE: tests/Browser/screenshots/.gitignore ================================================ * !.gitignore ================================================ FILE: tests/CreatesApplication.php ================================================ make(Kernel::class)->bootstrap(); return $app; } } ================================================ FILE: tests/DuskTestCase.php ================================================ addArguments([ '--disable-gpu', '--headless' ]); return RemoteWebDriver::create( 'http://localhost:9515', DesiredCapabilities::chrome()->setCapability( ChromeOptions::CAPABILITY, $options ) ); } } ================================================ FILE: tests/Feature/AttackTest.php ================================================ create(); $this->actingAs($user); /** @var Character $strongOwnCharacter */ $strongOwnCharacter = factory(Character::class)->create([ 'user_id' => $user, 'hit_points' => 1000, 'strength' => 1000, 'agility' => 1000, 'constitution' => 1000, 'intelligence' => 1000, 'charisma' => 1000, ]); $basicOpponentCharacter = factory(Character::class)->create([ 'location_id' => $strongOwnCharacter->location_id ]); // When we hit home folder $response = $this->post("/character/{$basicOpponentCharacter->id}/attack"); $battle = Battle::query()->firstOrFail(); // We are redirected to the Battle page $response->assertRedirect(route('battle.show', $battle->id)); $this->assertEquals($strongOwnCharacter->id, $battle->attacker_id); $this->assertEquals($strongOwnCharacter->id, $battle->victor_id); $this->assertEquals($basicOpponentCharacter->id, $battle->defender_id); } /** * @test */ public function basic_own_character_looses_while_attacking_strong_opponent(): void { // Given we have a signed in user that already has a strongCharacter $user = factory(User::class)->create(); $this->actingAs($user); /** @var Character $basicOwnCharacter */ $basicOwnCharacter = factory(Character::class)->create([ 'user_id' => $user, ]); $strongOpponentCharacter = factory(Character::class)->create([ 'location_id' => $basicOwnCharacter->location_id, 'hit_points' => 1000, 'strength' => 1000, 'agility' => 1000, 'constitution' => 1000, 'intelligence' => 1000, 'charisma' => 1000, ]); // When we hit home folder $response = $this->post("/character/{$strongOpponentCharacter->id}/attack"); $battle = Battle::query()->firstOrFail(); // We are redirected to the Battle page $response->assertRedirect(route('battle.show', $battle->id)); $this->assertEquals($basicOwnCharacter->id, $battle->attacker_id); $this->assertEquals($strongOpponentCharacter->id, $battle->victor_id); $this->assertEquals($strongOpponentCharacter->id, $battle->defender_id); } } ================================================ FILE: tests/Feature/CharacterCreationTest.php ================================================ create(); $this->actingAs($user); // When we hit home folder $response = $this->get('/home'); // We are redirected to the Character creation page $response->assertRedirect(route('character.create')); } /** * @test */ public function an_authenticated_user_with_character_record_is_redirected_to_character_location_page() { // Given we have a signed in user that already has a character $user = factory(User::class)->create(); $this->actingAs($user); $character = factory(Character::class)->create(['user_id' => $user]); // When we hit home folder $response = $this->get('/home'); // We are redirected to the Character creation page $response->assertRedirect(route('location.show', $character->location)); } } ================================================ FILE: tests/Feature/ExampleTest.php ================================================ get('/'); $response->assertStatus(200); } } ================================================ FILE: tests/TestCase.php ================================================ characterRepository = Mockery::mock(CharacterRepositoryInterface::class); $this->itemRepository = Mockery::mock(ItemRepositoryInterface::class); $this->itemPrototypeRepository = Mockery::mock(ItemPrototypeRepositoryInterface::class); $this->character = Mockery::mock(Character::class); $this->sut = new ItemService( $this->characterRepository, $this->itemRepository, $this->itemPrototypeRepository, new ItemFactory() ); } public function testCreate(): void { // Arrange $itemId = ItemId::fromString('598d1570-e0e3-40d1-979b-64e48626f777'); $itemPrototypeId = ItemPrototypeId::fromString('598d1570-e0e3-40d1-979b-64e48626f6f6'); $name = 'Wooden club'; $description = 'Club made from wood'; $type = ItemType::mainHand(); $price = ItemPrice::ofAmount(30); $effects = Collection::make([ ItemEffect::damage(5) ]); $creatorCharacterId = CharacterId::fromString('65976e46-d2eb-4373-ba69-b7c9ea81b56f'); $imageFilePath = 'images\equipment\main_hand\1club.png'; $itemPrototype = new ItemPrototype( $itemPrototypeId, $name, $description, $imageFilePath, $type, $effects, $price ); $createCommand = new CreateItemCommand($itemPrototypeId, $creatorCharacterId); $this->characterRepository->shouldReceive('getOne')->once()->andReturn($this->character); $this->itemPrototypeRepository->shouldReceive('getOne')->once()->andReturn($itemPrototype); $this->itemRepository->shouldReceive('nextIdentity')->once()->andReturn($itemId); $this->character->shouldReceive('getId')->once()->andReturn($creatorCharacterId); $this->character->shouldReceive('addItemToInventory')->once(); $this->itemRepository->shouldReceive('add')->once() ->with(\Mockery::on(static function (Item $item) use ($itemId, $name, $description, $imageFilePath, $type, $effects, $itemPrototypeId, $creatorCharacterId, $price) { // Assert Assert::assertEquals($itemId, $item->getId()); Assert::assertEquals($name, $item->getName()); Assert::assertEquals($description, $item->getDescription()); Assert::assertEquals($imageFilePath, $item->getImageFilePath()); Assert::assertEquals($type, $item->getType()); Assert::assertEquals($effects, $item->getEffects()); Assert::assertEquals($itemPrototypeId, $item->getPrototypeId()); Assert::assertEquals($creatorCharacterId, $item->getCreatorCharacterId()); Assert::assertEquals($price, $item->getPrice()); return true; })); $this->characterRepository->shouldReceive('update')->once(); // Act $this->sut->create($createCommand); } } ================================================ FILE: tests/Unit/app/Modules/Equipment/Domain/InventoryTest.php ================================================ id = Mockery::mock(InventoryId::class); $this->characterId = Mockery::mock(CharacterId::class); $this->money = Mockery::mock(Money::class); } public function testWillThrowExceptionOnTryingToCreateInventoryWithNonInventoryItems(): void { $this->expectException(InvalidArgumentException::class); new Inventory( $this->id, $this->characterId, Collection::make([ Mockery::mock(Item::class), ]), $this->money ); } public function testWillThrowExceptionOnTryingToCreateInventoryWithTooManyItems(): void { $items = $this->generateItems(Inventory::NUMBER_OF_SLOTS + 1); $this->expectException(NotEnoughSpaceInContainerException::class); new Inventory( $this->id, $this->characterId, $items, $this->money ); } public function testCreatingNewInventoryWithMaximumNumberOfItemsWorks(): void { $numberOfSlots = Inventory::NUMBER_OF_SLOTS; $items = $this->generateItems($numberOfSlots); $sut = new Inventory( $this->id, $this->characterId, $items, $this->money ); $this->assertSame($numberOfSlots, $sut->getItems()->count()); } public function testCreatingNewInventoryWithNoItemsWorks(): void { $sut = new Inventory( $this->id, $this->characterId, new Collection(), $this->money ); $this->assertSame(0, $sut->getItems()->count()); } public function testRightAmountOfMoneyAfterPuttingMoneyIn(): void { $initialMoney = new Money(5); $sut = new Inventory( $this->id, $this->characterId, $this->generateItems(5), $initialMoney ); $this->assertSame( $initialMoney->getValue(), $sut->getMoney()->getValue() ); } public function testRightAmountOfMoneyAfterPuttingMoreMoneyIn(): void { $initialMoney = new Money(5); $additionalMoney = new Money(4); $sut = new Inventory( $this->id, $this->characterId, $this->generateItems(5), $initialMoney ); $sut->putMoneyIn($additionalMoney); $this->assertSame( $initialMoney->getValue() + $additionalMoney->getValue(), $sut->getMoney()->getValue() ); } public function testRightAmountOfMoneyAfterTakingMoneyOut(): void { $initialMoney = new Money(5); $moneyToTakeOut = new Money(4); $sut = new Inventory( $this->id, $this->characterId, $this->generateItems(5), $initialMoney ); $sut->takeMoneyOut($moneyToTakeOut); $this->assertSame( $initialMoney->getValue() - $moneyToTakeOut->getValue(), $sut->getMoney()->getValue() ); } public function testAddingItemSequentially(): void { $initialNumberOfItems = $additionalItemOffset = 5; $additionalItem = Mockery::mock(Item::class); $sut = new Inventory( $this->id, $this->characterId, $this->generateItems($initialNumberOfItems), $this->money ); $additionalItem->shouldReceive('getId')->atLeast()->once(); $additionalItem->shouldReceive('getName')->atLeast()->once(); $additionalItem->shouldReceive('getDescription')->atLeast()->once(); $additionalItem->shouldReceive('getImageFilePath')->atLeast()->once(); $additionalItem->shouldReceive('getType')->atLeast()->once(); $additionalItem->shouldReceive('getEffects')->atLeast()->once(); $additionalItem->shouldReceive('getPrice')->atLeast()->once(); $additionalItem->shouldReceive('getPrototypeId')->atLeast()->once(); $additionalItem->shouldReceive('getCreatorCharacterId')->atLeast()->once(); $sut->add($additionalItem); $this->assertSame($initialNumberOfItems + 1, $sut->getItems()->count()); $this->assertEquals( new InventoryItem($additionalItem, ItemStatus::inBackpack()), $sut->getItems()->offsetGet($additionalItemOffset) ); } private function generateItems(int $numberOfItems): Collection { return Collection::make(array_map(static function () { return Mockery::mock(InventoryItem::class); }, range(0, $numberOfItems - 1))); } } ================================================ FILE: tests/Unit/app/Modules/Level/Application/Services/LevelServiceTest.php ================================================ levelService = new LevelService(); } public function xpForLevelServiceProvider() { $levels = (new LevelService())->getLevels(10); $levelMiddlePoints = array_map(static function (Level $level) { $middlePoint = floor(($level->getCurrentXpThreshold() + $level->getNextXpThreshold()) / 2); return [ 'xp' => (int)$middlePoint, 'levelId' => $level->getId(), ]; }, $levels); return $levelMiddlePoints; } /** * @dataProvider xpForLevelServiceProvider * * @param int $xp * @param int $levelId */ public function testGetLevelByXp(int $xp, int $levelId): void { $level = $this->levelService->getLevelByXp($xp); $this->assertEquals($levelId, $level->getId()); } } ================================================ FILE: tests/Unit/app/Modules/Level/Domain/Entities/LevelTest.php ================================================ getProgress(75); $this->assertEquals(50, $progress); } public function testGetProgressWithXpNotInRange() { $level = new Level(1, 50, 100); $progress = $level->getProgress(5); $this->assertEquals(0, $progress); } } ================================================ FILE: webpack.mix.js ================================================ const mix = require('laravel-mix'); /* |-------------------------------------------------------------------------- | Mix Asset Management |-------------------------------------------------------------------------- | | Mix provides a clean, fluent API for defining some Webpack build steps | for your Laravel application. By default, we are compiling the Sass | file for the application as well as bundling up all the JS files. | */ mix.js('resources/js/app.js', 'public/js') .vue() .sass('resources/sass/app.scss', 'public/css');