diff options
28 files changed, 529 insertions, 235 deletions
diff --git a/.env.example b/.env.example index ffe11a1..0d176c7 100644 --- a/.env.example +++ b/.env.example @@ -13,3 +13,4 @@ BASE_UNIT_RESOURCE_REQUIREMENT_FACTOR=64 BASE_RESOURCE_GENERATION_FACTOR=128 BASE_STORAGE_CAPACITY_FACTOR=2048 SATISFACTION_FOOD_THRESHOLD=10 +MAP_DEFAULT_RANGE=3 @@ -117,12 +117,12 @@ DB::query(<<<SQL constraint "relation_event" foreign key ("event_id") references events("id") on delete cascade, - "village_id" bigint not null, - constraint "relation_village" - foreign key ("village_id") references villages("id") on delete cascade, - "type" character varying(255) not null, + "wood" bigint not null, + "clay" bigint not null, + "iron" bigint not null, + "created_at" timestamp(0) not null default current_timestamp, "updated_at" timestamp(0) not null default current_timestamp ); @@ -136,13 +136,13 @@ DB::query(<<<SQL constraint "relation_event" foreign key ("event_id") references events("id") on delete cascade, - "village_id" bigint not null, - constraint "relation_village" - foreign key ("village_id") references villages("id") on delete cascade, - "amount" bigint not null, "type" character varying(255) not null, + "wood" bigint not null, + "clay" bigint not null, + "iron" bigint not null, + "created_at" timestamp(0) not null default current_timestamp, "updated_at" timestamp(0) not null default current_timestamp ); @@ -220,7 +220,7 @@ DB::query(<<<SQL "unit_id" bigint not null, constraint "relation_unit" - foreign key ("unit") references village_units("id"), + foreign key ("unit_id") references village_units("id"), "amount" bigint not null, diff --git a/composer.lock b/composer.lock index 2524e07..0cf0a5d 100644 --- a/composer.lock +++ b/composer.lock @@ -12,7 +12,7 @@ "source": { "type": "git", "url": "https://git.dweipert.de/gemini-foundation", - "reference": "3776ab80f23c07943d2228f2975e515c494f930a" + "reference": "faf9ddfc29676b86621bdc3033e05d63df5c2a93" }, "default-branch": true, "type": "library", @@ -30,7 +30,7 @@ "email": "code@drogueronin.de" } ], - "time": "2024-01-02T19:39:07+00:00" + "time": "2024-01-15T12:54:52+00:00" }, { "name": "symfony/config", @@ -38,12 +38,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "5d33e0fb707d603330e0edfd4691803a1253572e" + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/5d33e0fb707d603330e0edfd4691803a1253572e", - "reference": "5d33e0fb707d603330e0edfd4691803a1253572e", + "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", "shasum": "" }, "require": { @@ -105,7 +105,7 @@ "type": "tidelift" } ], - "time": "2023-11-09T08:28:32+00:00" + "time": "2024-11-04T11:33:53+00:00" }, { "name": "symfony/deprecation-contracts", @@ -113,12 +113,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -127,12 +127,12 @@ "default-branch": true, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { @@ -157,7 +157,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/main" }, "funding": [ { @@ -173,7 +173,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/dotenv", @@ -181,12 +181,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "835f8d2d1022934ac038519de40b88158798c96f" + "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/835f8d2d1022934ac038519de40b88158798c96f", - "reference": "835f8d2d1022934ac038519de40b88158798c96f", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/1ac5e7e7e862d4d574258daf08bd569ba926e4a5", + "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5", "shasum": "" }, "require": { @@ -247,20 +247,20 @@ "type": "tidelift" } ], - "time": "2023-12-28T19:16:56+00:00" + "time": "2024-11-27T11:08:19+00:00" }, { "name": "symfony/filesystem", - "version": "7.1.x-dev", + "version": "7.3.x-dev", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "2d3a1adb578bd4e783b5f1cbfc4e2c12ae05b466" + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/2d3a1adb578bd4e783b5f1cbfc4e2c12ae05b466", - "reference": "2d3a1adb578bd4e783b5f1cbfc4e2c12ae05b466", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", "shasum": "" }, "require": { @@ -268,6 +268,9 @@ "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, "type": "library", "autoload": { "psr-4": { @@ -294,7 +297,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/7.1" + "source": "https://github.com/symfony/filesystem/tree/v7.2.0" }, "funding": [ { @@ -310,7 +313,7 @@ "type": "tidelift" } ], - "time": "2023-12-19T09:30:49+00:00" + "time": "2024-10-25T15:15:23+00:00" }, { "name": "symfony/http-foundation", @@ -318,12 +321,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "172d807f9ef3fc3fbed8377cc57c20d389269271" + "reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/172d807f9ef3fc3fbed8377cc57c20d389269271", - "reference": "172d807f9ef3fc3fbed8377cc57c20d389269271", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0492d6217e5ab48f51fca76f64cf8e78919d0db", + "reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db", "shasum": "" }, "require": { @@ -333,12 +336,12 @@ "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.3" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.3|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", @@ -387,7 +390,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T22:16:42+00:00" + "time": "2025-01-09T15:48:56+00:00" }, { "name": "symfony/polyfill-ctype", @@ -395,16 +398,16 @@ "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -415,12 +418,9 @@ "default-branch": true, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -454,7 +454,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -470,7 +470,7 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -478,16 +478,17 @@ "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -498,12 +499,9 @@ "default-branch": true, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -538,7 +536,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/1.x" }, "funding": [ { @@ -554,91 +552,7 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "1.x-dev", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "default-branch": true, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php83", @@ -646,27 +560,23 @@ "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-php80": "^1.14" + "php": ">=7.2" }, "default-branch": true, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -703,7 +613,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -719,7 +629,7 @@ "type": "tidelift" } ], - "time": "2023-08-16T06:22:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/routing", @@ -727,12 +637,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "98eab13a07fddc85766f1756129c69f207ffbc21" + "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/98eab13a07fddc85766f1756129c69f207ffbc21", - "reference": "98eab13a07fddc85766f1756129c69f207ffbc21", + "url": "https://api.github.com/repos/symfony/routing/zipball/e9bfc94953019089acdfb9be51c1b9142c4afa68", + "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68", "shasum": "" }, "require": { @@ -802,7 +712,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:34:34+00:00" + "time": "2025-01-09T08:51:02+00:00" }, { "name": "twig/twig", @@ -810,24 +720,24 @@ "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "b4c3c1c448583e9ffa24933675c01f4be3435b41" + "reference": "8432946eeeca009d75fc7fc568f3c3f4650f5a0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/b4c3c1c448583e9ffa24933675c01f4be3435b41", - "reference": "b4c3c1c448583e9ffa24933675c01f4be3435b41", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8432946eeeca009d75fc7fc568f3c3f4650f5a0f", + "reference": "8432946eeeca009d75fc7fc568f3c3f4650f5a0f", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22" + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { + "phpstan/phpstan": "^2.0", "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "default-branch": true, "type": "library", @@ -882,7 +792,7 @@ "type": "tidelift" } ], - "time": "2024-01-01T14:44:03+00:00" + "time": "2025-02-04T11:35:15+00:00" } ], "packages-dev": [], @@ -896,6 +806,6 @@ "platform": { "php": "^8.0" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/docker-compose.yml b/docker-compose.yml index c2a6b6a..3ee2d28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,6 @@ -version: "3" - services: db: image: postgres - restart: unless-stopped ports: - "5432:5432" environment: @@ -19,7 +16,7 @@ services: volumes: - "./:/var/www/html" - web: + http: build: context: docker/nginx ports: diff --git a/public/assets/style.css b/public/assets/style.css index 300d282..b17d5ae 100644 --- a/public/assets/style.css +++ b/public/assets/style.css @@ -14,18 +14,22 @@ body { } -button, input[type="submit"] { +button, +input[type="submit"] { background-color: #1e362e; color: #fff; border: none; padding: 0.25rem 0.75rem; + cursor: pointer; } -button:hover:not(:disabled), input[type="submit"]:hover:not(:disabled) { +button:hover:not(:disabled), +input[type="submit"]:hover:not(:disabled) { background-color: #000; } -button:disabled, input[type="submit"]:disabled { +button:disabled, +input[type="submit"]:disabled { color: #606060; } @@ -71,14 +75,17 @@ button:disabled, input[type="submit"]:disabled { .icon-arrow-up { background-image: url("/assets/img/icons/openmojis/1F53A.svg"); } + .icon-arrow-down { background-image: url("/assets/img/icons/openmojis/1F53A.svg"); transform: rotate(180deg); } + .icon-arrow-left { background-image: url("/assets/img/icons/openmojis/1F53A.svg"); transform: rotate(270deg); } + .icon-arrow-right { background-image: url("/assets/img/icons/openmojis/1F53A.svg"); transform: rotate(90deg); @@ -108,7 +115,7 @@ button:disabled, input[type="submit"]:disabled { grid-template-columns: 2fr 2fr 2fr 2fr 1fr; } -.resources > div { +.resources>div { padding: 0.25rem; border: 1px solid #000; @@ -116,7 +123,7 @@ button:disabled, input[type="submit"]:disabled { align-items: center; } -.resources > div > *:not(:first-child) { +.resources>div>*:not(:first-child) { margin-left: 0.25rem; } @@ -124,6 +131,10 @@ button:disabled, input[type="submit"]:disabled { font-size: 1.25em; } +.resources .icon-storage { + cursor: pointer; +} + /* Map */ @@ -133,7 +144,8 @@ button:disabled, input[type="submit"]:disabled { padding: 2rem; } -.map__up, .map__down { +.map__up, +.map__down { position: absolute; width: 100%; text-align: center; @@ -149,7 +161,8 @@ button:disabled, input[type="submit"]:disabled { left: 0; } -.map__left, .map__right { +.map__left, +.map__right { position: absolute; height: 100%; @@ -193,11 +206,27 @@ button:disabled, input[type="submit"]:disabled { display: flex; justify-content: center; align-items: center; + + position: relative; } -.map__village a { +.map__village--center, +.map__village:hover { + border-color: #999; +} + +.map__village a:not(.center-anchor) { text-decoration: none; border: 2px solid #fff; padding: 0 0.25rem; background: rgba(255, 255, 255, 0.8); } + +.map__village .center-anchor { + position: absolute; + left: 5px; + top: 0; + + text-decoration: none; + color: #fff; +}
\ No newline at end of file @@ -3,7 +3,12 @@ mkShell { buildInputs = [ php phpPackages.composer + phpactor nodejs + nodePackages.typescript-language-server + vscode-langservers-extracted + dockerfile-language-server-nodejs + docker-compose-language-service just entr ]; diff --git a/src/Model/Event/BaseEvent.php b/src/Model/Event/BaseEvent.php index 2e2564b..0d50458 100644 --- a/src/Model/Event/BaseEvent.php +++ b/src/Model/Event/BaseEvent.php @@ -15,16 +15,15 @@ abstract class BaseEvent public Event $event; + abstract function create(): void; + abstract function cancel(): void; abstract function dbInsert(): void; abstract function dbDelete(): void; - abstract function cancel(): void; - public function getEvent(): Event + public function populateEvent(): void { if (! isset($this->event)) { $this->event = DB::fetch(Event::class, 'select * from events where id=:id', ['id' => $this->eventId])[0]; } - - return $this->event; } } diff --git a/src/Model/Event/SendResources.php b/src/Model/Event/SendResources.php index ea9361c..d8e4f85 100644 --- a/src/Model/Event/SendResources.php +++ b/src/Model/Event/SendResources.php @@ -94,7 +94,7 @@ class SendResources extends BaseEvent public function cancel(): void { - $this->event = $this->getEvent(); + $this->populateEvent(); $cancelTimeDiff = $this->event->createdAt->diff(new \DateTime()); $cancelTime = (new \DateTime())->add($cancelTimeDiff); diff --git a/src/Model/Event/SendResourcesCarriers.php b/src/Model/Event/SendResourcesCarriers.php index b6c3772..d129732 100644 --- a/src/Model/Event/SendResourcesCarriers.php +++ b/src/Model/Event/SendResourcesCarriers.php @@ -34,6 +34,9 @@ class SendResourcesCarriers extends BaseEvent } } + public function cancel(): void + {} + public function dbInsert(): void { DB::query( diff --git a/src/Model/Event/SendUnits.php b/src/Model/Event/SendUnits.php index c9afd6e..1b04333 100644 --- a/src/Model/Event/SendUnits.php +++ b/src/Model/Event/SendUnits.php @@ -104,7 +104,7 @@ class SendUnits extends BaseEvent public function cancel(): void { - $this->event = $this->getEvent(); + $this->populateEvent(); $cancelTimeDiff = $this->event->createdAt->diff(new \DateTime()); $cancelTime = (new \DateTime())->add($cancelTimeDiff); diff --git a/src/Model/Event/TrainUnits.php b/src/Model/Event/TrainUnits.php index 59b169f..a94a5a8 100644 --- a/src/Model/Event/TrainUnits.php +++ b/src/Model/Event/TrainUnits.php @@ -14,7 +14,7 @@ class TrainUnits extends BaseEvent */ public function __invoke(): void { - $this->getEvent(); + $this->populateEvent(); DB::query( <<<SQL diff --git a/src/Model/Event/UpgradeBuilding.php b/src/Model/Event/UpgradeBuilding.php index a3f3984..5993105 100644 --- a/src/Model/Event/UpgradeBuilding.php +++ b/src/Model/Event/UpgradeBuilding.php @@ -3,17 +3,21 @@ namespace App\Model\Event; use App\DB; +use App\Model\Village; class UpgradeBuilding extends BaseEvent { public string $type; + public int $wood; + public int $clay; + public int $iron; /** * @return void */ public function __invoke(): void { - $this->getEvent(); + $this->populateEvent(); DB::query( <<<SQL @@ -26,8 +30,44 @@ class UpgradeBuilding extends BaseEvent ); } + /*public function populateEvent(): void + { + parent::populateEvent(); + + $event = DB::fetch(UpgradeBuilding::class, 'select * from events_upgrade_building where id=:id', ['id' => $this->id])[0]; + $this->type = $event->type; + $this->wood = $event->wood; + $this->clay = $event->clay; + $this->iron = $event->iron; + }*/ + + public function create(): void + { + $this->populateEvent(); + + // remove resources + $village = Village::get($this->event->villageId); + $village->wood -= $this->wood; + $village->clay -= $this->clay; + $village->iron -= $this->iron; + $village->updateResources(); + + // add to db + $this->dbInsert(); + } + public function cancel(): void { + $this->populateEvent(); + + // add resources + $village = Village::get($this->event->villageId); + $village->wood += $this->wood; + $village->clay += $this->clay; + $village->iron += $this->iron; + $village->updateResources(); + + // remove from db $this->dbDelete(); } @@ -39,8 +79,14 @@ class UpgradeBuilding extends BaseEvent ); DB::query( - 'insert into events_upgrade_building (event_id, type) VALUES (:event_id, :type)', - ['event_id' => DB::$connection->lastInsertId(), 'type' => $this->type] + 'insert into events_upgrade_building (event_id, type, wood, clay, iron) VALUES (:event_id, :type, :wood, :clay, :iron)', + [ + 'event_id' => DB::$connection->lastInsertId(), + 'type' => $this->type, + 'wood' => $this->wood, + 'clay' => $this->clay, + 'iron' => $this->iron, + ] ); } diff --git a/src/Model/Village.php b/src/Model/Village.php index ff0b7e8..8ecee2a 100644 --- a/src/Model/Village.php +++ b/src/Model/Village.php @@ -184,7 +184,7 @@ class Village * * @return array<int, Unit> */ - public static function getUnits(int $villageId, int $fetchFlag = Village::FETCH_UNIT_ALL, int $returnFlag = Village::RETURN_UNIT_EXISTING): array + public static function getUnits(int $villageId, int $fetchFlag = Village::FETCH_UNIT_RESIDENCE, int $returnFlag = Village::RETURN_UNIT_EXISTING): array { if ($fetchFlag == Village::FETCH_UNIT_HOME_AT_HOME) { $units = DB::fetch(Unit::class, 'select * from village_units where home_village_id=:id and residence_village_id=:id', ['id' => $villageId]); diff --git a/src/Support/BuildingType.php b/src/Support/BuildingType.php new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/Support/BuildingType.php diff --git a/src/View.php b/src/View.php index 10ce9db..761ef62 100644 --- a/src/View.php +++ b/src/View.php @@ -25,6 +25,10 @@ class View // self::$twig->addExtension(new IntlExtension()); self::$twig->addFilter(new TwigFilter('buildTime', function ($buildTime) { + if ($buildTime > 3600*24) { + return @sprintf('%02d:%02d:%02d:%02d', $buildTime / (3600*24), $buildTime / 3600, ($buildTime / 60) % 60, $buildTime % 60); + } + return @sprintf('%02d:%02d:%02d', $buildTime / 3600, ($buildTime / 60) % 60, $buildTime % 60); })); } diff --git a/src/http/Controller/Building.php b/src/http/Controller/Building.php index 4a59f0e..d434fe8 100644 --- a/src/http/Controller/Building.php +++ b/src/http/Controller/Building.php @@ -21,10 +21,7 @@ class Building $building = Model::getByVillage($village->id, $request->get('type')) ?? Model::getEmpty($village->id, $request->get('type')); // resources - foreach ($building->getResourceRequirements() as $resourceType => $resourceValue) { - $village->{$resourceType} -= $resourceValue; - } - $village->updateResources(); + $resourceRequirements = $building->getResourceRequirements(); // event $event = new Event(); @@ -35,7 +32,10 @@ class Building $upgradeBuildingEvent = new UpgradeBuilding(); $upgradeBuildingEvent->event = $event; $upgradeBuildingEvent->type = $building->type; - $upgradeBuildingEvent->dbInsert(); + $upgradeBuildingEvent->wood = $resourceRequirements['wood']; + $upgradeBuildingEvent->clay = $resourceRequirements['clay']; + $upgradeBuildingEvent->iron = $resourceRequirements['iron']; + $upgradeBuildingEvent->create(); return new RedirectResponse( Router::generate( diff --git a/src/http/Controller/Event.php b/src/http/Controller/Event.php index 8b9b92a..264fa26 100644 --- a/src/http/Controller/Event.php +++ b/src/http/Controller/Event.php @@ -8,7 +8,10 @@ use App\Model\Event\SendResources; use App\Model\Event\SendUnits; use App\Model\Event\TrainUnits; use App\Model\Event\UpgradeBuilding; +use App\Model\Unit; +use App\Model\Unit\MailCarrier; use App\Model\Village; +use App\View; use App\http\Router; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -20,6 +23,39 @@ class Event #[Route(path: '/village/{x}/{y}/send-resources', methods: ['POST'])] public function sendResources(Request $request): Response { + $village = Village::getByCoordinates($request->get('x'), $request->get('y')); + + $totalAmount = $request->get('wood') + $request->get('clay') + $request->get('iron') + $request->get('food'); + + $resourceCapabilities = MailCarrier::getResourceCapabilities($village); + $necessaryMailCarriers = ceil($totalAmount / $resourceCapabilities); + $mailCarriers = DB::fetch(MailCarrier::class, 'select sum(amount) as amount from village_units where type=:type and residence_village_id=:villageId and is_traveling=false', ['villageId' => $village->id, 'type' => 'MailCarrier'])[0]->amount ?? 0; + if ($mailCarriers === 0 || $totalAmount > $resourceCapabilities) { + return new Response(View::render('error.twig', ['message' => 'Insufficient Mail Carriers']), 403); + } + + $destination = Village::get($request->get('village')); + + // event + $event = new Model(); + $event->time = (new \DateTime())->add( + \DateInterval::createFromDateString( + Unit::getTravelTime(new MailCarrier(), Village::getDistance($village->x, $village->y, $destination->x, $destination->y)) + . ' seconds' + ) + ); + $event->villageId = $village->id; + $sendResourcesEvent = new SendResources(); + $sendResourcesEvent->event = $event; + $sendResourcesEvent->wood = $request->get('wood'); + $sendResourcesEvent->clay = $request->get('clay'); + $sendResourcesEvent->iron = $request->get('iron'); + $sendResourcesEvent->food = $request->get('food'); + $sendResourcesEvent->source = $village->id; + $sendResourcesEvent->destination = $destination->id; + $sendResourcesEvent->dbInsert(); + + return new RedirectResponse( Router::generate( 'village.show', diff --git a/src/http/Controller/Login.php b/src/http/Controller/Login.php index 8c04d85..37680a7 100644 --- a/src/http/Controller/Login.php +++ b/src/http/Controller/Login.php @@ -3,6 +3,7 @@ namespace App\http\Controller; use App\DB; +use App\Model\Building\Farm; use App\View; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -26,15 +27,59 @@ class Login if (empty($user)) { $password = password_hash($request->get('password'), PASSWORD_DEFAULT); DB::query('insert into users (username, password, email) values (:username, :password, :email)', ['username' => $email, 'password' => $password, 'email' => $email]); + + $userId = DB::$connection->lastInsertId(); - // TODO: also insert new village at random free coordinates + // insert new village at random free coordinates + # TODO: check if coords are free + DB::query( + 'insert into villages (name, x, y, wood, clay, iron, food, satisfaction) values (:name, :x, :y, 100, 100, 100, 100, 100)', + [ + 'name' => $email, + 'x' => random_int(-10, 10), + 'y' => random_int(-10, 10), + ] + ); + $villageId = DB::$connection->lastInsertId(); + + DB::query('insert into user_villages (user_id, village_id) values (:user_id, :village_id)', [ + 'user_id' => $userId, + 'village_id' => $villageId, + ]); + + DB::query('insert into village_storage_config (wood, clay, iron, food, village_id) values (:wood, :clay, :iron, :food, :village_id)', [ + 'wood' => 25, + 'clay' => 25, + 'iron' => 25, + 'food' => 25, + 'village_id' => $village->id, + ]); + + $initialBuildings = ['TownHall', 'Storage', 'WoodCutter', 'ClayPit', 'IronMine', 'Farm']; + foreach ($initialBuildings as $buildingType) { + DB::query('insert into village_buildings (level, type, village_id) values (:level, :type, :village_id)', [ + 'level' => 1, + 'type' => $buildingType, + 'village_id' => $villageId, + ]); + } + + $initialUnits = ['WoodCutter', 'PitWorker', 'Miner', 'Farmer']; + foreach ($initialUnits as $unitType) { + DB::query('insert into village_units (amount, type, home_village_id, residence_village_id) values (:amount, :type, :village_id, :village_id)', [ + 'amount' => 1, + 'type' => $unitType, + 'village_id' => $villageId, + ]); + } } else { $password = $user['password']; + $userId = $user['id']; } if (password_verify($request->get('password'), $password)) { $_SESSION['user'] = [ - 'id' => $user['id'], + 'id' => $userId, 'username' => $user['username'], ]; diff --git a/src/http/Controller/Map.php b/src/http/Controller/Map.php index 69d23e1..3b0773f 100644 --- a/src/http/Controller/Map.php +++ b/src/http/Controller/Map.php @@ -10,13 +10,17 @@ use Symfony\Component\Routing\Annotation\Route; class Map { - #[Route(path: '/map/{x}/{y}/{range}', defaults: ['range' => 1], methods: ['GET'])] + #[Route(path: '/map/{x}/{y}/{range}', defaults: ['range' => 0], methods: ['GET'])] public function region(Request $request): Response { $x = $request->get('x'); $y = $request->get('y'); $range = $request->get('range'); + if ($range <= 0) { + $range = $_ENV['MAP_DEFAULT_RANGE']; + } + $statement = DB::query( 'select * from villages where x>=:x1 and x<=:x2 and y>=:y1 and y<=:y2', [ diff --git a/src/http/Controller/User.php b/src/http/Controller/User.php new file mode 100644 index 0000000..e38780f --- /dev/null +++ b/src/http/Controller/User.php @@ -0,0 +1,58 @@ +<?php + +namespace App\http\Controller; + +use App\DB; +use App\View; +use App\http\Router; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; + +class User +{ + #[Route(path: '/account', methods: ['GET'])] + public function account(Request $request): Response + { + $user = DB::query('select username,email from users where id=:id', ['id' => $_SESSION['user']['id']])->fetch(); + + return new Response(View::render('account.twig', [ + 'user' => $user, + ])); + } + + #[Route(path: '/account', methods: ['POST'])] + public function accountSave(Request $request): Response + { + $username = $request->get('username'); + $email = $request->get('email'); + + if ($request->get('password')) { + $password = password_hash($request->get('password'), PASSWORD_DEFAULT); + DB::query( + 'update users set username=:username, email=:email, password=:password where id=:id', + [ + 'username' => $username, + 'email' => $email, + 'password' => $password, + 'id' => $_SESSION['user']['id'], + ] + ); + } else { + DB::query( + 'update users set username=:username, email=:email where id=:id', + [ + 'username' => $username, + 'email' => $email, + 'id' => $_SESSION['user']['id'], + ] + ); + } + + $_SESSION['user']['username'] = $request->get('username'); + + + return new RedirectResponse(Router::generate('user.account')); + } +} diff --git a/src/http/Controller/Village.php b/src/http/Controller/Village.php index 7b1f227..22713b1 100644 --- a/src/http/Controller/Village.php +++ b/src/http/Controller/Village.php @@ -4,6 +4,8 @@ namespace App\http\Controller; use App\DB; use App\Guard; +use App\Model\Event\SendResources; +use App\Model\Event\SendResourcesCarriers; use App\Model\Event\SendUnits; use App\Model\Event\TrainUnits; use App\Model\Event\UpgradeBuilding; @@ -27,7 +29,7 @@ class Village join user_villages on villages.id = user_villages.village_id where user_villages.user_id=:id SQL, - ['id' => $_SESSION['user']['id']] + ['id' => $_SESSION['user']['id'] ?? -1] ); return new Response(View::render('villages.twig', [ @@ -55,26 +57,32 @@ class Village )->fetchAll(); foreach ($eventsBuilding as $row) { - $events['UpgradeBuilding'][$row['type']][] = DB::convertToModel(UpgradeBuilding::class, $row); + $model = DB::convertToModel(UpgradeBuilding::class, $row); + $model->populateEvent(); + + $events['UpgradeBuilding'][$row['type']][] = $model; } $eventsUnits = DB::query( <<<SQL select * from events_train_units as event left join events on event.event_id = events.id - where village_id=:id + where events.village_id=:id SQL, ['id' => $village->id] )->fetchAll(); foreach ($eventsUnits as $row) { - $events['TrainUnits'][] = DB::convertToModel(TrainUnits::class, $row); + $model = DB::convertToModel(TrainUnits::class, $row); + $model->populateEvent(); + + $events['TrainUnits'][] = $model; } $eventsUnitsSendOwn = DB::query( <<<SQL select * from events_send_units as event left join events on event.event_id = events.id - where village_id=:id + where events.village_id=:id SQL, ['id' => $village->id] )->fetchAll(); @@ -87,7 +95,56 @@ class Village )->fetchAll(); foreach ([...$eventsUnitsSendOwn, ...$eventsUnitsSendOther] as $row) { - $events['SendUnits'][] = DB::convertToModel(SendUnits::class, $row);; + $model = DB::convertToModel(SendUnits::class, $row); + $model->populateEvent(); + + $events['SendUnits'][] = DB::convertToModel(SendUnits::class, $row); + } + + $eventsResourcesSendOwn = DB::query( + <<<SQL + select * from events_send_resources as event + left join events on event.event_id = events.id + where events.village_id=:id + SQL, ['id' => $village->id] + )->fetchAll(); + + $eventsResourcesSendOther = DB::query( + <<<SQL + select * from events_send_resources as event + left join events on event.event_id = events.id + where (destination=:id or source=:id) and village_id!=:id and is_canceled=false + SQL, ['id' => $village->id] + )->fetchAll(); + + foreach ([...$eventsResourcesSendOwn, ...$eventsResourcesSendOther] as $row) { + $model = DB::convertToModel(SendResources::class, $row); + $model->populateEvent(); + + $events['SendResources'][] = DB::convertToModel(SendResources::class, $row); + } + + $eventsResourcesCarriersSendOwn = DB::query( + <<<SQL + select * from events_send_resources_carriers as event + left join events on event.event_id = events.id + where events.village_id=:id + SQL, ['id' => $village->id] + )->fetchAll(); + + $eventsResourcesCarriersSendOther = DB::query( + <<<SQL + select * from events_send_resources_carriers as event + left join events on event.event_id = events.id + where (destination=:id or source=:id) and village_id!=:id + SQL, ['id' => $village->id] + )->fetchAll(); + + foreach ([...$eventsResourcesCarriersSendOwn, ...$eventsResourcesCarriersSendOther] as $row) { + $model = DB::convertToModel(SendResourcesCarriers::class, $row); + $model->populateEvent(); + + $events['SendResourcesCarriers'][] = $model; } $buildings = []; diff --git a/src/http/Router.php b/src/http/Router.php index db75f81..f279109 100644 --- a/src/http/Router.php +++ b/src/http/Router.php @@ -9,7 +9,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Generator\UrlGenerator; -use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Routing\Loader\AttributeFileLoader; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouteCollection; @@ -28,7 +28,7 @@ class Router self::$context->fromRequest($request); self::$routes = new RouteCollection(); - $loader = new AnnotationFileLoader(new FileLocator(), new RouteLoader()); + $loader = new AttributeFileLoader(new FileLocator(), new RouteLoader()); $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__ . '/Controller')); foreach ($iterator as $file) { /**@var \SplFileInfo $file*/ diff --git a/src/http/Support/RouteLoader.php b/src/http/Support/RouteLoader.php index b0e74cb..d11011c 100644 --- a/src/http/Support/RouteLoader.php +++ b/src/http/Support/RouteLoader.php @@ -2,10 +2,10 @@ namespace App\http\Support; -use Symfony\Component\Routing\Loader\AnnotationClassLoader; +use Symfony\Component\Routing\Loader\AttributeClassLoader; use Symfony\Component\Routing\Route; -class RouteLoader extends AnnotationClassLoader +class RouteLoader extends AttributeClassLoader { protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annotation) { $route->setDefault('_', compact('class', 'method', 'annotation')); @@ -15,10 +15,9 @@ class RouteLoader extends AnnotationClassLoader { $name = parent::getDefaultRouteName($class, $method); - return str_replace( - '_', - '.', - str_replace('app_controller_', '', $name) - ); + $name = preg_replace('/app_\w+_controller_/', '', $name); + $name = str_replace('_', '.', $name); + + return $name; } } diff --git a/views/http/account.twig b/views/http/account.twig new file mode 100644 index 0000000..8b32feb --- /dev/null +++ b/views/http/account.twig @@ -0,0 +1,22 @@ +{% extends 'base.twig' %} + +{% block main %} +<h1>Account</h1> + +<form action="/account" method="post"> + <label> + Username + <input type="text" name="username" value="{{ user.username }}"> + </label> + <label> + E-Mail + <input type="email" name="email" value="{{ user.email }}"> + </label> + <label> + Password + <input type="password" name="password"> + </label> + + <input type="submit" value="Save"> +</form> +{% endblock %} diff --git a/views/http/base.twig b/views/http/base.twig index 15ddafd..bd59207 100644 --- a/views/http/base.twig +++ b/views/http/base.twig @@ -5,7 +5,12 @@ <header> <nav> <a href="/villages">Overview</a> - <a href="/logout">Logout</a> + {% if session.user %} + <a href="/account">Account</a> + <a href="/logout">Logout</a> + {% else %} + <a href="/login">Login</a> + {% endif %} </nav> <span>Logged in as {{ session.user.username }}</span> </header> diff --git a/views/http/components/timer.twig b/views/http/components/timer.twig index ccb31a7..4898a2e 100644 --- a/views/http/components/timer.twig +++ b/views/http/components/timer.twig @@ -14,12 +14,18 @@ document.addEventListener('DOMContentLoaded', function (ev) { window.location.reload(); } + const dd = Math.floor(diff/1000/60/60/24); + diff -= dd*1000*60*60*24; const hh = Math.floor(diff/1000/60/60); diff -= hh*1000*60*60; const mm = Math.floor(diff/1000/60); diff -= mm*1000*60; const ss = Math.floor(diff/1000); + timer.innerHTML = `${('00' + hh).slice(-2)}:${('00' + mm).slice(-2)}:${('00' + ss).slice(-2)}`; + if (dd > 0) { + timer.innerHTML = `${('00' + dd).slice(-2)}:${timer.innerHTML}`; + } } setTime(); }); diff --git a/views/http/map.twig b/views/http/map.twig index 29f0294..a05fea2 100644 --- a/views/http/map.twig +++ b/views/http/map.twig @@ -3,13 +3,13 @@ {% block main %} <div class="map"> <div class="map__up"> - <a href="/map/{{ x }}/{{ y - 1 }}"> + <a href="/map/{{ x }}/{{ y - 1 }}/{{ range }}"> <i class="icon icon-arrow-up"></i> </a> </div> <div> <div class="map__left"> - <a href="/map/{{ x - 1 }}/{{ y }}"> + <a href="/map/{{ x - 1 }}/{{ y }}/{{ range }}"> <i class="icon icon-arrow-left"></i> </a> </div> @@ -17,7 +17,9 @@ {% for row in range(-range, range) %} {% for column in range(-range, range) %} {% set village = map[x + column][y + row] %} - <div class="map__village"> + <div class="map__village {% if row == 0 and column == 0 %}map__village--center{% endif %}"> + <!-- TODO: add link to center on tile in top left --> + <a class="center-anchor" title="move to center" href="/map/{{ x + column }}/{{ y + row }}/{{ range }}">c</a> {% if village %} <a href="/village/{{ village.x }}/{{ village.y }}"> {{ map[x + column][y + row].name }} @@ -28,13 +30,13 @@ {% endfor %} </div> <div class="map__right"> - <a href="/map/{{ x + 1 }}/{{ y }}"> + <a href="/map/{{ x + 1 }}/{{ y }}/{{ range }}"> <i class="icon icon-arrow-right"></i> </a> </div> </div> <div class="map__down"> - <a href="/map/{{ x }}/{{ y + 1 }}"> + <a href="/map/{{ x }}/{{ y + 1 }}/{{ range }}"> <i class="icon icon-arrow-down"></i> </a> </div> diff --git a/views/http/village.twig b/views/http/village.twig index c157a04..48bb16d 100644 --- a/views/http/village.twig +++ b/views/http/village.twig @@ -92,9 +92,11 @@ {% include 'components/timer.twig' with { 'time': event.event.time|date('c') } %} </td> <td> - <a class="btn" href="/village/{{ village.x }}/{{ village.y }}/building/UpgradeBuilding/build/cancel"> - Cancel - </a> + {% if event.event.villageId == village.id %} + <form action="/event/{{ event.event.id }}/cancel" method="post"> + <input type="submit" value="Cancel"> + </form> + {% endif %} </td> </tr> {% endfor %} @@ -121,9 +123,11 @@ {% include 'components/timer.twig' with { 'time': event.event.time|date('c') } %} </td> <td> - <a class="btn" href="/village/{{ village.x }}/{{ village.y }}/unit/train/cancel"> - Cancel - </a> + {% if event.event.villageId == village.id %} + <form action="/event/{{ event.event.id }}/cancel" method="post"> + <input type="submit" value="Cancel"> + </form> + {% endif %} </td> </tr> {% endfor %} @@ -132,7 +136,7 @@ {% endif %} {% if events['SendUnits'] %} - <h4>Send Resources / Units</h4> + <h4>Send Units</h4> <table> <thead> <tr> @@ -172,6 +176,68 @@ </tbody> </table> {% endif %} + + {% if events['SendResources'] %} + <h4>Send Resources</h4> + <table> + <thead> + <tr> + <th>Source</th> + <th>Destination</th> + <th>Resources</th> + <th>Time</th> + <th></th> + </tr> + </thead> + <tbody> + {% for event in events['SendResources'] %} + <tr> + <td>{{ village.get(event.source).name }}</td> + <td>{{ village.get(event.destination).name }}</td> + <td>Resources</td> + <td class="timer"> + {% include 'components/timer.twig' with { 'time': event.event.time|date('c') } %} + </td> + <td> + {% if event.isCanceled %} + Canceled + {% else %} + {% if event.event.villageId == village.id %} + <form action="/event/{{ event.event.id }}/cancel" method="post"> + <input type="submit" value="Cancel"> + </form> + {% endif %} + {% endif %} + </td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} + + {% if events['SendResourcesCarriers'] %} + <h4>Send Resources Carriers</h4> + <table> + <thead> + <tr> + <th>Source</th> + <th>Destination</th> + <th>Time</th> + </tr> + </thead> + <tbody> + {% for event in events['SendResourcesCarriers'] %} + <tr> + <td>{{ village.get(event.source).name }}</td> + <td>{{ village.get(event.destination).name }}</td> + <td class="timer"> + {% include 'components/timer.twig' with { 'time': event.event.time|date('c') } %} + </td> + </tr> + {% endfor %} + </tbody> + </table> + {% endif %} </div> <div class="village__main"> @@ -344,10 +410,10 @@ {% if village.getBuilding(village.id, 'PostOffice') %} <h3>Send Resources</h3> <form action="/village/{{ village.x }}/{{ village.y }}/send-resources" method="post"> - <input type="number" min="1" name="wood" placeholder="Amount Wood" required> - <input type="number" min="1" name="clay" placeholder="Amount Clay" required> - <input type="number" min="1" name="iron" placeholder="Amount Iron" required> - <input type="number" min="1" name="food" placeholder="Amount Food" required> + <input type="number" min="0" name="wood" placeholder="Amount Wood" required> + <input type="number" min="0" name="clay" placeholder="Amount Clay" required> + <input type="number" min="0" name="iron" placeholder="Amount Iron" required> + <input type="number" min="0" name="food" placeholder="Amount Food" required> <select name="village"> {% for v in villages %} <option value="{{ v.id }}">{{ v.name }}</option> |