diff options
author | Daniel Weipert <git@mail.dweipert.de> | 2024-01-02 20:42:01 +0100 |
---|---|---|
committer | Daniel Weipert <git@mail.dweipert.de> | 2024-01-05 12:33:59 +0100 |
commit | b21316248572cb27ed1f504529ad6680a473022e (patch) | |
tree | f8a2f81258cae3b1d2429fb7df5a3287954b683a /src/gemini | |
parent | f621d95f89ded05a2e916c5ee363bfe75ea37482 (diff) |
gemini
Diffstat (limited to 'src/gemini')
-rw-r--r-- | src/gemini/Controller/Building.php | 44 | ||||
-rw-r--r-- | src/gemini/Controller/Unit.php | 60 | ||||
-rw-r--r-- | src/gemini/Controller/User.php | 118 | ||||
-rw-r--r-- | src/gemini/Controller/Village.php | 167 | ||||
-rw-r--r-- | src/gemini/Gemini.php | 120 |
5 files changed, 509 insertions, 0 deletions
diff --git a/src/gemini/Controller/Building.php b/src/gemini/Controller/Building.php new file mode 100644 index 0000000..9827d00 --- /dev/null +++ b/src/gemini/Controller/Building.php @@ -0,0 +1,44 @@ +<?php + +namespace App\gemini\Controller; + +use App\Model\Building as Model; +use App\Model\Event; +use App\Model\Event\UpgradeBuilding; +use App\Model\Village; +use GeminiFoundation\Request; +use GeminiFoundation\Response; +use GeminiFoundation\Status; + +class Building +{ + //#[Route(path: '/village/{x}/{y}/building/{type}/level-up', methods: ['POST'])] + public function levelUp(Request $request): Response + { + $village = Village::getByCoordinates($request->get('x'), $request->get('y')); + $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(); + + // event + $event = new Event(); + $event->time = (new \DateTime())->add(\DateInterval::createFromDateString( + $building->getBuildTimeForLevel($building->getEffectiveLevel() + 1) . ' seconds' + )); + $event->villageId = $building->villageId; + $upgradeBuildingEvent = new UpgradeBuilding(); + $upgradeBuildingEvent->event = $event; + $upgradeBuildingEvent->type = $building->type; + $upgradeBuildingEvent->dbInsert(); + + + return new Response( + statusCode: Status::REDIRECT_TEMPORARY, # correct response code? + meta: "/village/{$village->x}/{$village->y}" + ); + } +} diff --git a/src/gemini/Controller/Unit.php b/src/gemini/Controller/Unit.php new file mode 100644 index 0000000..c04079a --- /dev/null +++ b/src/gemini/Controller/Unit.php @@ -0,0 +1,60 @@ +<?php + +namespace App\gemini\Controller; + +use App\Model\Event; +use App\Model\Event\TrainUnits; +use App\Model\Unit as Model; +use App\Model\Village; +use GeminiFoundation\Request; +use GeminiFoundation\Response; +use GeminiFoundation\Status; + +class Unit +{ + // #[Route(path: '/village/{x}/{y}/unit/{type}/create', methods: ['POST'])] + public function train(Request $request): Response + { + if (empty($request->get('input'))) { + return new Response(statusCode: Status::INPUT, meta: 'Amount'); + } + + $village = Village::getByCoordinates($request->get('x'), $request->get('y')); + + /**@var Model $unit*/ + $unit = new (Model::resolveType($request->get('type')))(); + $unit->type = $request->get('type'); + $unit->homeVillageId = $village->id; + + $amount = intval($request->get('input')); + + if (! Village::canTrain($village, $unit, $amount)) { + return new Response( + statusCode: Status::REDIRECT_TEMPORARY, + meta: "/village/{$village->x}/{$village->y}" + ); + } + + // resources + foreach (Model::getResourceRequirements($unit, $amount) as $resourceType => $resourceValue) { + $village->{$resourceType} -= $resourceValue; + } + $village->updateResources(); + + // event + $event = new Event(); + $event->time = (new \DateTime())->add(\DateInterval::createFromDateString($unit->getBuildTime($amount) . ' seconds')); + $event->villageId = $village->id; + $trainUnitsEvent = new TrainUnits(); + $trainUnitsEvent->event = $event; + $trainUnitsEvent->type = $request->get('type'); + $trainUnitsEvent->amount = $amount; + $trainUnitsEvent->dbInsert(); + + + return new Response( + statusCode: Status::REDIRECT_TEMPORARY, + meta: "/village/{$village->x}/{$village->y}" + ); + } +} diff --git a/src/gemini/Controller/User.php b/src/gemini/Controller/User.php new file mode 100644 index 0000000..a870b88 --- /dev/null +++ b/src/gemini/Controller/User.php @@ -0,0 +1,118 @@ +<?php + +namespace App\gemini\Controller; + +use App\DB; +use GeminiFoundation\Request; + +class User +{ + public function get(Request $request): array|bool + { + if ($request->getClientCertificate() === null) { + return false; + } + + return DB::query( + <<<SQL + select users.id, username, email from users + join users_gemini on users.id = users_gemini.user_id + where users_gemini.certificate=:fingerprint + SQL, + ['fingerprint' => $request->getClientCertificate()->getFingerprint()] + )->fetch(); + } + + public function create(Request $request): array|bool + { + DB::query( + 'insert into users (username, password, email) values (:username, :fingerprint, :email)', + [ + 'username' => md5($request->getClientCertificate()->getFingerprint()), + 'fingerprint' => $request->getClientCertificate()->getFingerprint(), + 'email' => '(no email)', + ] + ); + $userId = DB::query('select id from users where password=:password', ['password' => $request->getClientCertificate()->getFingerprint()])->fetchColumn(); + + DB::query( + 'insert into users_gemini (certificate, user_id) values (:fingerprint, :userId)', + ['fingerprint' => $request->getClientCertificate()->getFingerprint(), 'userId' => $userId] + ); + + // also insert new village at random free coordinates + DB::query( + 'insert into villages (name, x, y, wood, clay, iron, food, satisfaction) values (:name, :x, :y, :wood, :clay, :iron, :food, :satisfaction)', + [ + 'name' => substr(md5(rand()), 0, 6), + 'x' => rand(0, 100), + 'y' => rand(0, 100), + 'wood' => 500, + 'clay' => 500, + 'iron' => 500, + 'food' => 500, + 'satisfaction' => 100, + ] + ); + $villageId = DB::query('select id from villages order by id desc limit 1')->fetchColumn(); + + DB::query( + 'insert into user_villages (user_id, village_id) values (:userId, :villageId)', + ['userId' => $userId, 'villageId' => $villageId] + ); + + // insert base buildings + DB::query( + 'insert into village_buildings (level, type, village_id) values (:level, :type, :villageId)', + ['level' => 1, 'type' => 'TownHall', 'villageId' => $villageId] + ); + + DB::query( + 'insert into village_buildings (level, type, village_id) values (:level, :type, :villageId)', + ['level' => 1, 'type' => 'Storage', 'villageId' => $villageId] + ); + DB::query( + 'insert into village_storage_config (wood, clay, iron, food, village_id) values (:wood, :clay, :iron, :food, :villageId)', + ['wood' => 25, 'clay' => 25, 'iron' => 25, 'food' => 25, 'villageId' => $villageId] + ); + + DB::query( + 'insert into village_buildings (level, type, village_id) values (:level, :type, :villageId)', + ['level' => 1, 'type' => 'WoodCutter', 'villageId' => $villageId] + ); + DB::query( + 'insert into village_units (amount, type, is_traveling, home_village_id, residence_village_id) values (:amount, :type, false, :villageId, :villageId)', + ['amount' => 1, 'type' => 'WoodCutter', 'villageId' => $villageId] + ); + + DB::query( + 'insert into village_buildings (level, type, village_id) values (:level, :type, :villageId)', + ['level' => 1, 'type' => 'ClayPit', 'villageId' => $villageId] + ); + DB::query( + 'insert into village_units (amount, type, is_traveling, home_village_id, residence_village_id) values (:amount, :type, false, :villageId, :villageId)', + ['amount' => 1, 'type' => 'PitWorker', 'villageId' => $villageId] + ); + + DB::query( + 'insert into village_buildings (level, type, village_id) values (:level, :type, :villageId)', + ['level' => 1, 'type' => 'IronMine', 'villageId' => $villageId] + ); + DB::query( + 'insert into village_units (amount, type, is_traveling, home_village_id, residence_village_id) values (:amount, :type, false, :villageId, :villageId)', + ['amount' => 1, 'type' => 'Miner', 'villageId' => $villageId] + ); + + DB::query( + 'insert into village_buildings (level, type, village_id) values (:level, :type, :villageId)', + ['level' => 1, 'type' => 'Farm', 'villageId' => $villageId] + ); + DB::query( + 'insert into village_units (amount, type, is_traveling, home_village_id, residence_village_id) values (:amount, :type, false, :villageId, :villageId)', + ['amount' => 1, 'type' => 'Farmer', 'villageId' => $villageId] + ); + + + return $this->get($request); + } +} diff --git a/src/gemini/Controller/Village.php b/src/gemini/Controller/Village.php new file mode 100644 index 0000000..9b27561 --- /dev/null +++ b/src/gemini/Controller/Village.php @@ -0,0 +1,167 @@ +<?php + +namespace App\gemini\Controller; + +use App\DB; +use App\Guard; +use App\Model\Event\SendUnits; +use App\Model\Event\TrainUnits; +use App\Model\Event\UpgradeBuilding; +use App\Model\Village as Model; +use App\View; +use GeminiFoundation\Request; +use GeminiFoundation\Response; +use GeminiFoundation\Status; + +class Village +{ + // #[Route(path: '/villages', methods: ['GET'])] + public function list(): Response + { + $villages = DB::fetch( + Model::class, + <<<SQL + select * from villages + join user_villages on villages.id = user_villages.village_id + where user_villages.user_id=:id + SQL, + ['id' => $_SESSION['user']['id']] + ); + + + return new Response(body: View::render('villages.twig', [ + 'villages' => $villages, + ])); + } + + //#[Route(path: '/village/{x}/{y}', methods: ['GET'])] + public function show(Request $request): Response + { + $village = Model::getByCoordinates($request->get('x'), $request->get('y')); + + if (! Guard::ownsVillage($village->id)) { + return new Response(body: View::render('error.twig', ['message' => 'Insufficient permission'])); + } + + $events = []; + + $eventsBuilding = DB::query( + <<<SQL + select * from events_upgrade_building as event + left join events on event.event_id = events.id + where events.village_id=:id + SQL, ['id' => $village->id] + )->fetchAll(); + + foreach ($eventsBuilding as $row) { + $events['UpgradeBuilding'][$row['type']][] = DB::convertToModel(UpgradeBuilding::class, $row); + } + + $eventsUnits = DB::query( + <<<SQL + select * from events_train_units as event + left join events on event.event_id = events.id + where village_id=:id + SQL, ['id' => $village->id] + )->fetchAll(); + + foreach ($eventsUnits as $row) { + $events['TrainUnits'][] = DB::convertToModel(TrainUnits::class, $row); + } + + $eventsUnitsSendOwn = DB::query( + <<<SQL + select * from events_send_units as event + left join events on event.event_id = events.id + where village_id=:id + SQL, ['id' => $village->id] + )->fetchAll(); + + $eventsUnitsSendOther = DB::query( + <<<SQL + select * from events_send_units 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 ([...$eventsUnitsSendOwn, ...$eventsUnitsSendOther] as $row) { + $events['SendUnits'][] = DB::convertToModel(SendUnits::class, $row);; + } + + $buildings = []; + foreach (Model::getBuildings($village->id, true) as $building) { + $buildings[$building->type] = $building; + } + + + return new Response(body: View::render('village.twig', [ + 'village' => $village, + 'events' => $events, + 'buildings' => $buildings, + 'villages' => DB::fetch(Model::class, "select * from villages where id!=:id", ['id' => $village->id]), + ])); + } + + // #[Route(path: '/village/{x}/{y}/storage/config', methods: ['POST'])] + public function storageConfig(Request $request): Response + { + $village = Model::getByCoordinates($request->get('x'), $request->get('y')); + $type = $request->get('type'); + + if (empty($type)) { + return new Response(body: View::render('storage.twig', [ + 'village' => $village, + ])); + } + + if (empty($request->get('input'))) { + return new Response(statusCode: Status::INPUT, meta: "$type percent?"); + } + + $input = intval($request->get('input')); + + // calculate to max 100% + $allTypes = ['wood', 'clay', 'iron', 'food']; + $allOtherTypes = array_diff($allTypes, [$type]); + + $storageConfig = $village->getStorageConfig($village->id); + + $values = []; + foreach ($allTypes as $resourceType) { + $values[$resourceType] = $storageConfig->$resourceType; + } + $values[$type] = $input; + + $total = 0; + foreach ($values as $value) { + $total += $value; + } + + foreach ($values as $resourceType => $value) { + $values[$resourceType] = round(($value / $total) * 100); + } + + if ($values[$type] !== $input) { + $values[$type] = $input; + } + + $newTotal = array_sum($values); + $values['food'] += (100 - $newTotal); + + DB::query( + <<<SQL + update village_storage_config + set wood=:wood, clay=:clay, iron=:iron, food=:food + where village_id=:id + SQL, + ['wood' => $values['wood'], 'clay' => $values['clay'], 'iron' => $values['iron'], 'food' => $values['food'], 'id' => $village->id] + ); + + + return new Response( + statusCode: Status::REDIRECT_TEMPORARY, + meta: "/village/{$village->x}/{$village->y}/storage/config" + ); + } +} diff --git a/src/gemini/Gemini.php b/src/gemini/Gemini.php new file mode 100644 index 0000000..41b8716 --- /dev/null +++ b/src/gemini/Gemini.php @@ -0,0 +1,120 @@ +<?php + +namespace App\gemini; + +use App\DB; +use App\EventRunner; +use App\View; +use App\gemini\Controller\Building; +use App\gemini\Controller\Unit; +use App\gemini\Controller\User; +use App\gemini\Controller\Village; +use GeminiFoundation\Request; +use GeminiFoundation\Response; +use GeminiFoundation\Server; +use GeminiFoundation\Status; + +class Gemini +{ + private array $certificate; + private string $hostname; + + public function __construct(array $certificate, string $hostname) + { + $this->certificate = $certificate; + $this->hostname = $hostname; + + global $_SESSION; + $_SESSION = []; + + DB::init(); + + View::init(); + View::addGlobal('session', $_SESSION); + } + + public function run(): void + { + $server = new Server($this->certificate, $this->hostname, [ + 'client_certificate_support_workaround' => true, + ]); + + $server->onRequest(function (Response $response, Request $request) { + new EventRunner(); + + // auth + if ($request->getClientCertificate() === null) { + return new Response( + statusCode: Status::CLIENT_CERTIFICATE_REQUIRED, + meta: 'Attach a client certificate to log in' + ); + } + + $userController = new User(); + $user = $userController->get($request); + if (empty($user)) { + $user = $userController->create($request); + } + + global $_SESSION; + $_SESSION['user'] = [ + 'id' => $user['id'], + 'username' => $user['username'], + ]; + View::addGlobal('session', $_SESSION); + + + // routes + if ($request->getPath() == '/villages') { + $villageController = new Village(); + $response = $villageController->list($request); + } + + else if (preg_match('@village/(\d+)/(\d+)/storage/config/?(\w+)?@', $request->getPath(), $routeMatch)) { + $request + ->set('x', $routeMatch[1]) + ->set('y', $routeMatch[2]); + + if (isset($routeMatch[3])) { + $request->set('type', $routeMatch[3]); + } + + $villageController = new Village(); + $response = $villageController->storageConfig($request); + } + + else if (preg_match('@village/(\d+)/(\d+)/building/(\w+)/level-up@', $request->getPath(), $routeMatch)) { + $request + ->set('x', $routeMatch[1]) + ->set('y', $routeMatch[2]) + ->set('type', $routeMatch[3]); + + $buildingController = new Building(); + $response = $buildingController->levelUp($request); + } + + else if (preg_match('@village/(\d+)/(\d+)/unit/(\w+)/create@', $request->getPath(), $routeMatch)) { + $request + ->set('x', $routeMatch[1]) + ->set('y', $routeMatch[2]) + ->set('type', $routeMatch[3]); + + $unitController = new Unit(); + $response = $unitController->train($request); + } + + else if (preg_match('@village/(\d+)/(\d+)@', $request->getPath(), $routeMatch)) { + $request + ->set('x', $routeMatch[1]) + ->set('y', $routeMatch[2]); + + $villageController = new Village(); + $response = $villageController->show($request); + } + + return $response; + }); + + $server->listen(); + } +} |