From cbdfca18c66a221c594d7f7e56d8cce011397a85 Mon Sep 17 00:00:00 2001 From: Temirkhan Date: Thu, 31 Aug 2023 23:06:33 +0200 Subject: [PATCH 01/29] Removing empty entries from inventory simplified --- src/Player/Player.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Player/Player.php b/src/Player/Player.php index f4af29b..6ff6ce9 100644 --- a/src/Player/Player.php +++ b/src/Player/Player.php @@ -7,7 +7,6 @@ use Game\Dungeon\Dungeon; use Game\Dungeon\TTKCalculator; use Game\Engine\DBConnection; -use Game\Engine\DbTimeFactory; use Game\Engine\Error; use Game\Item\Item; use Game\Item\ItemPrototype as ItemPrototype; @@ -20,17 +19,6 @@ class Player private readonly PlayerLog $logger; - /** - * @param string $name - * @param DBConnection $connection - * @return self - * @deprecated will be removed - */ - public static function loadPlayer(int $id, DBConnection $connection): self - { - return new self($id, $connection); - } - /** * @internal Should not be used outside the current module */ @@ -264,10 +252,7 @@ public function dropItem(ItemPrototype $item, int $quantity): Drop if ($remainingItemsQuantity < 0) { throw new \RuntimeException('Player does not have that many items'); } - - if ($remainingItemsQuantity === 0) { - $this->destroyItem($item); - } + $this->removeNonExistentItems(); }); return new Drop($item, $quantity); From 7b075f449f1474f8560fa3b43ab8a71773983ac0 Mon Sep 17 00:00:00 2001 From: Temirkhan Date: Thu, 31 Aug 2023 23:19:40 +0200 Subject: [PATCH 02/29] Perks are now applied to player stats respectively --- src/Player/CreateCharacter.php | 24 ++++++++++++++++++++-- src/Player/Player.php | 4 ++-- src/UI/Template/component/player-info.twig | 12 +++++------ tests/Player/CreateCharacterTest.php | 18 ++++++++++------ updates/20230831211107.sql | 1 + 5 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 updates/20230831211107.sql diff --git a/src/Player/CreateCharacter.php b/src/Player/CreateCharacter.php index 1578750..e110efc 100644 --- a/src/Player/CreateCharacter.php +++ b/src/Player/CreateCharacter.php @@ -20,7 +20,21 @@ public function __construct( public function execute(string $characterName, Race $race, User $forUser): Player { $this->db->execute( - "INSERT INTO players(user_id, race, name, health, health_max, strength, defence) VALUE (?, ?, ?, ?, ?, ?, ?)", + "INSERT INTO players( + user_id, + race, + name, + health, + health_max, + strength, + defence, + woodcutting, + harvesting, + mining, + blacksmith, + gathering, + alchemy + ) VALUE (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ $forUser->id, $race->id, @@ -28,7 +42,13 @@ public function execute(string $characterName, Race $race, User $forUser): Playe $race->stats->maxHealth, $race->stats->maxHealth, $race->stats->strength, - $race->stats->defence + $race->stats->defence, + $race->perks->canWoodcut ? 1 : 0, + $race->perks->canHarvest ? 1 : 0, + $race->perks->canMine ? 1 : 0, + $race->perks->canCraft ? 1 : 0, + $race->perks->canGather ? 1 : 0, + $race->perks->canBrew ? 1 : 0, ] ); diff --git a/src/Player/Player.php b/src/Player/Player.php index 6ff6ce9..bf5e02d 100644 --- a/src/Player/Player.php +++ b/src/Player/Player.php @@ -208,9 +208,9 @@ public function getHarvesting(): int return (int) $this->getProperty('harvesting'); } - public function getHerbalism(): int + public function getAlchemy(): int { - return (int) $this->getProperty('herbalism'); + return (int) $this->getProperty('alchemy'); } public function getBlacksmith(): int diff --git a/src/UI/Template/component/player-info.twig b/src/UI/Template/component/player-info.twig index 07382a1..35438a6 100644 --- a/src/UI/Template/component/player-info.twig +++ b/src/UI/Template/component/player-info.twig @@ -26,12 +26,12 @@
Abilities
- Woodcutting: {{ player.woodcutting }}
- Mining: {{ player.mining }}
- Gathering: {{ player.gathering }}
- Harvesting: {{ player.harvesting }}
- Blacksmith: {{ player.blacksmith }}
- Herbalism: {{ player.herbalism }}
+ {% if player.woodcutting > 0 %}Lumberjack: {{ player.woodcutting }}
{% endif %} + {% if player.mining > 0 %}Miner: {{ player.mining }}
{% endif %} + {% if player.gathering > 0 %}Gatherer: {{ player.gathering }}
{% endif %} + {% if player.harvesting > 0 %}Farmer: {{ player.harvesting }}
{% endif %} + {% if player.blacksmith > 0 %}Craft: {{ player.blacksmith }}
{% endif %} + {% if player.alchemy > 0 %}Alchemist: {{ player.alchemy }}
{% endif %}
  • diff --git a/tests/Player/CreateCharacterTest.php b/tests/Player/CreateCharacterTest.php index 2a6a3d8..36a89b8 100644 --- a/tests/Player/CreateCharacterTest.php +++ b/tests/Player/CreateCharacterTest.php @@ -52,11 +52,17 @@ private function assertNewCharacterCreated(string $characterName, Race $expected self::assertEquals(10, $character->getGold()); self::assertFalse($character->isFighting()); self::assertTrue($character->isInProtectiveZone()); - self::assertEquals(0, $character->getWoodcutting()); - self::assertEquals(0, $character->getHerbalism()); - self::assertEquals(0, $character->getHarvesting()); - self::assertEquals(0, $character->getBlacksmith()); - self::assertEquals(0, $character->getMining()); - self::assertEquals(0, $character->getGathering()); + $lumberjackLvl = $expectedRace->perks->canWoodcut ? 1 : 0; + $minerLvl = $expectedRace->perks->canMine ? 1 : 0; + $craftsmanLvl = $expectedRace->perks->canCraft ? 1 : 0; + $gathererLvl = $expectedRace->perks->canGather ? 1 : 0; + $farmerLvl = $expectedRace->perks->canHarvest ? 1 : 0; + $alchemistLvl = $expectedRace->perks->canBrew ? 1 : 0; + self::assertEquals($lumberjackLvl, $character->getWoodcutting()); + self::assertEquals($alchemistLvl, $character->getAlchemy()); + self::assertEquals($farmerLvl, $character->getHarvesting()); + self::assertEquals($craftsmanLvl, $character->getBlacksmith()); + self::assertEquals($minerLvl, $character->getMining()); + self::assertEquals($gathererLvl, $character->getGathering()); } } diff --git a/updates/20230831211107.sql b/updates/20230831211107.sql new file mode 100644 index 0000000..7c3ca22 --- /dev/null +++ b/updates/20230831211107.sql @@ -0,0 +1 @@ +ALTER TABLE players RENAME COLUMN herbalism TO alchemy; From 3c4c3aa15062b0f88d35008e6570369fe1da6382 Mon Sep 17 00:00:00 2001 From: Temirkhan Date: Sat, 2 Sep 2023 01:59:50 +0200 Subject: [PATCH 03/29] Implemented raw luberjack(woodcutting) activity --- data/item.yaml | 8 ++ src/Dungeon/Reward.php | 17 +++- src/Player/Activity/ActivityInterface.php | 28 +++++++ src/Player/Activity/Lumberjack.php | 82 +++++++++++++++++++ src/Player/Player.php | 41 ++++++++++ src/Server.php | 44 ++++++++++ src/UI/Scene/Activity/Lumberjack.php | 69 ++++++++++++++++ src/UI/Template/activity/lumberjack.html.twig | 52 ++++++++++++ src/UI/Template/minimalistic.template.twig | 14 ++-- updates/20230831215628.sql | 12 +++ www/launcher.php | 4 + 11 files changed, 363 insertions(+), 8 deletions(-) create mode 100644 src/Player/Activity/ActivityInterface.php create mode 100644 src/Player/Activity/Lumberjack.php create mode 100644 src/UI/Scene/Activity/Lumberjack.php create mode 100644 src/UI/Template/activity/lumberjack.html.twig create mode 100644 updates/20230831215628.sql diff --git a/data/item.yaml b/data/item.yaml index 2538f6c..cd221f6 100644 --- a/data/item.yaml +++ b/data/item.yaml @@ -18,3 +18,11 @@ id: 5 name: Leather worth: 5 +- + id: 6 + name: Firewood + worth: 0 +- + id: 7 + name: Oka plank + worth: 1 diff --git a/src/Dungeon/Reward.php b/src/Dungeon/Reward.php index 8d668cf..a56bf98 100644 --- a/src/Dungeon/Reward.php +++ b/src/Dungeon/Reward.php @@ -36,7 +36,7 @@ public function isEmpty(): bool */ public function listDrop(): array { - return $this->drop; + return array_values($this->drop); } private function addDrop(Drop $drop): void @@ -49,4 +49,19 @@ private function addDrop(Drop $drop): void $this->drop[$itemId] = $drop; } } + + public function multiply(float $modifier): static + { + $newExp = (int) round($this->exp * $modifier); + $newDrops = []; + foreach ($this->drop as $drop) { + $newAmount = (int) round($drop->quantity * $modifier); + if ($newAmount === 0) { + continue; + } + $newDrops[] = new Drop($drop->item, $newAmount); + } + + return new self($newExp, $newDrops); + } } diff --git a/src/Player/Activity/ActivityInterface.php b/src/Player/Activity/ActivityInterface.php new file mode 100644 index 0000000..2c1db1c --- /dev/null +++ b/src/Player/Activity/ActivityInterface.php @@ -0,0 +1,28 @@ + [ + 'id' => 1, + 'name' => 'Hollow tree', + 'description' => 'Hollow tree. Can be sold as fuel for a coin or two.', + 'complexity' => 1, + 'rewardExp' => 5, + 'rewardItem' => 6, + ], + 2 => [ + 'id' => 2, + 'name' => 'Oak tree', + 'description' => 'Strong tree. They say hogs love hanging around it.', + 'complexity' => 3, + 'rewardExp' => 8, + 'rewardItem' => 7, + ], + ]; + + private int $optionId; + private string $optionName; + + public function __construct(int $option) + { + if (!isset(self::OPTIONS[$option])) { + throw new \RuntimeException('Unknown option passed'); + } + + $this->optionId = $option; + $this->optionName = self::OPTIONS[$option]['name']; + } + + public function getName(): string + { + return 'Lumberjack'; + } + + public function getOption(): int + { + return $this->optionId; + } + + public function getOptionName(): string + { + return $this->optionName; + } + + public function calculateReward(Player $for): Reward + { + $playerGeneralEfficiency = $for->getWoodcutting(); + if ($playerGeneralEfficiency === 0) { + return Reward::none(); + } + + $option = self::OPTIONS[$this->optionId]; + $efficiency = (int)round($playerGeneralEfficiency/$option['complexity']); + if ($efficiency === 0){ + return Reward::none(); + } + + $itemRepository = \DI::getService(ItemPrototypeRepository::class); + + return new Reward($option['rewardExp'], [new Drop($itemRepository->getById($option['rewardItem']), $efficiency)]); + } + + public function isSame(ActivityInterface $activity): bool + { + return $activity->getName() === $this->getName() && $activity->getOptionName() === $this->getOptionName(); + } +} diff --git a/src/Player/Player.php b/src/Player/Player.php index bf5e02d..13573af 100644 --- a/src/Player/Player.php +++ b/src/Player/Player.php @@ -10,6 +10,8 @@ use Game\Engine\Error; use Game\Item\Item; use Game\Item\ItemPrototype as ItemPrototype; +use Game\Player\Activity\ActivityInterface; +use Game\Player\Activity\Lumberjack; use Game\Skill\Effect\EffectApplier; use Game\Trade\Offer; @@ -137,6 +139,45 @@ public function addExp(int $amount): void $this->logger->add($this->id, "You gained $amount experience points."); } + public function startActivity(ActivityInterface $activity): ?Error + { + if (!$this->isInProtectiveZone()) { + return new Error('Can not go for activities when not in protective zone'); + } + + if ($this->getCurrentActivity() !== null) { + return new Error('Character is busy with another activity'); + } + + $this->connection->execute(" + INSERT INTO activity(character_id, name, selected_option) + VALUE (?, ?, ?) + ", [$this->id, $activity->getName(), $activity->getOption()]); + + return null; + } + + public function getCurrentActivity(): ?ActivityInterface + { + $data = $this->connection->fetchRow('SELECT name, selected_option FROM activity WHERE character_id=' . $this->id); + if ($data === []) { + return null; + } + + // TODO some inflector would be good to resolve the values + switch ($data['name']) { + case 'Lumberjack': + return new Lumberjack($data['selected_option']); + default: + throw new \RuntimeException('Unknown activity'); + } + } + + public function stopActivity(): void + { + $this->connection->execute('DELETE FROM activity WHERE character_id=' . $this->id); + } + public function getLevel(): int { return (int) $this->getProperty('level'); diff --git a/src/Server.php b/src/Server.php index 326c0c4..3a2f985 100644 --- a/src/Server.php +++ b/src/Server.php @@ -149,6 +149,50 @@ private function giveRewards(): void $this->logs[] = $rewardLog; } } + + $this->giveActivityRewards(); + } + + private function giveActivityRewards(): void + { + $lastCheckedAt = $this->currentTime->subMinute(); + + $activities = $this->db->fetchRows('SELECT character_id, last_reward_at, checked_at FROM activity WHERE checked_at < ?', [DbTimeFactory::createTimestamp($lastCheckedAt)]); + + foreach ($activities as $data) { + $character = $this->characterRepository->getById($data['character_id']); + $activity = $character->getCurrentActivity(); + + $lastCheckedAt = CarbonImmutable::create($data['checked_at']); + $lastRewardedAt = CarbonImmutable::create($data['last_reward_at']); + + $minutesSinceLastCheck = $lastCheckedAt->diffInMinutes($this->currentTime, false); + $minutesSinceLastReward = $lastRewardedAt->diffInMinutes($this->currentTime, false); + $remainingStamina = $character->getStamina(); + // If player spent in dungeon more time than stamina he has, decrease spent time according that amount + if ($remainingStamina < $minutesSinceLastCheck) { + $minutesSinceLastCheck = $remainingStamina; + $minutesSinceLastReward = $lastRewardedAt->diffInMinutes($lastCheckedAt, false) + $remainingStamina; + } + + $this->db->execute('UPDATE players SET stamina = GREATEST(stamina - ?, 0) WHERE id = ?', [$minutesSinceLastCheck, $character->getId()]); + $this->db->execute('UPDATE activity SET checked_at=? WHERE character_id=?', [DbTimeFactory::createTimestamp($this->currentTime), $character->getId()]); + + if ($minutesSinceLastReward < 60) { + continue; + } + + $fullHoursPassed = (int) ($minutesSinceLastReward/60); + $rewardPerHour = $activity->calculateReward($character); + $reward = $rewardPerHour->multiply($fullHoursPassed); + + $character->addExp($reward->exp); + foreach ($reward->listDrop() as $drop) { + $character->pickUp($drop); + } + + $this->db->execute('UPDATE activity SET last_reward_at=last_reward_at + INTERVAL ? HOUR WHERE character_id=?', [$fullHoursPassed, $character->getId()]); + } } /** diff --git a/src/UI/Scene/Activity/Lumberjack.php b/src/UI/Scene/Activity/Lumberjack.php new file mode 100644 index 0000000..2616725 --- /dev/null +++ b/src/UI/Scene/Activity/Lumberjack.php @@ -0,0 +1,69 @@ +getCurrentPlayer(); + // Player can not perform this activity + if ($player->getWoodcutting() < 1) { + return $this->switchToScene(MainMenu::class); + } + $errorMsg = ''; + $infoMsg = ''; + + $this->handleInput($input, $player); + + $currentActivity = $player->getCurrentActivity(); + $options = []; + foreach (\Game\Player\Activity\Lumberjack::OPTIONS as $option) { + $activity = new \Game\Player\Activity\Lumberjack($option['id']); + $option['isCurrent'] = $currentActivity !== null && $activity->isSame($currentActivity); + $reward = $activity->calculateReward($player); + if ($reward->isEmpty()) { + $option['gainPerHour'] = null; + } else { + $option['gainPerHour'] = [ + 'exp' => $reward->exp, + 'drop' => $reward->listDrop()[0], + ]; + } + $options[] = $option; + } + + return $this->renderTemplate('activity/lumberjack', [ + 'options' => $options, + 'errorMsg' => $errorMsg, + 'infoMsg' => $infoMsg, + ]); + } + + private function handleInput(InputInterface $input, Player $player): void + { + if ($input->getString('action') === 'stop') { + $player->stopActivity(); + + return; + } + + $selectedOption = $input->getInt('tree'); + if ($selectedOption > 0) { + foreach (\Game\Player\Activity\Lumberjack::OPTIONS as $option) { + // If option exists then start activity + if ($selectedOption === $option['id']) { + $player->startActivity(new \Game\Player\Activity\Lumberjack($option['id'])); + return; + } + } + } + } +} diff --git a/src/UI/Template/activity/lumberjack.html.twig b/src/UI/Template/activity/lumberjack.html.twig new file mode 100644 index 0000000..be833cb --- /dev/null +++ b/src/UI/Template/activity/lumberjack.html.twig @@ -0,0 +1,52 @@ +{% extends "minimalistic.template.twig" %} + +{% block content %} +
    +
    Lumberjack
    +
    +
    +
    + {% if errorMsg %} +
    {{ errorMsg }}

    + {% elseif infoMsg %} +
    {{ infoMsg }}

    + {% endif %} + {% set column = 0 %} + {% for option in options %} + {% set column = column + 1 %} + {% if column == 1 %} +
    + {% endif %} +
    +

    {{ option.name }}

    + {% if option.gainPerHour is not null %} + Per hour
    + Exp: {{ option.gainPerHour.exp }}
    + Resource: {{ option.gainPerHour.drop.quantity }} {{ option.gainPerHour.drop.item.name }}
    + {% else %} +
    +
    + Skill level is too low
    + {% endif %} + {% if option.isCurrent %} + + {% else %} + + {% endif %} +
    + {% if column == 2 %} + {% set column = 0 %} +
    +
    + {% endif %} + {% endfor %} + {# helps to close an unclosed div-row #} + {% if column == 1 %} +
    +
    + {% endif %} +
    +
    +
    + +{% endblock %} diff --git a/src/UI/Template/minimalistic.template.twig b/src/UI/Template/minimalistic.template.twig index 43017b3..743a51b 100644 --- a/src/UI/Template/minimalistic.template.twig +++ b/src/UI/Template/minimalistic.template.twig @@ -48,15 +48,15 @@
  • -
  • -
    -
    Abilities
    -
    - - {% if player.woodcutting > 0 %}Lumberjack: {{ player.woodcutting }}
    {% endif %} - {% if player.mining > 0 %}Miner: {{ player.mining }}
    {% endif %} - {% if player.gathering > 0 %}Gatherer: {{ player.gathering }}
    {% endif %} - {% if player.harvesting > 0 %}Farmer: {{ player.harvesting }}
    {% endif %} - {% if player.blacksmith > 0 %}Craft: {{ player.blacksmith }}
    {% endif %} - {% if player.alchemy > 0 %}Alchemist: {{ player.alchemy }}
    {% endif %} -
    -
  • Bank
    diff --git a/src/UI/Template/component/player-quick-actions.twig b/src/UI/Template/component/player-quick-actions.twig new file mode 100644 index 0000000..ccb1d4a --- /dev/null +++ b/src/UI/Template/component/player-quick-actions.twig @@ -0,0 +1,17 @@ +
    + +
    +
    diff --git a/src/UI/Template/component/player-status.twig b/src/UI/Template/component/player-status.twig deleted file mode 100644 index c1c7a6e..0000000 --- a/src/UI/Template/component/player-status.twig +++ /dev/null @@ -1,27 +0,0 @@ -
    -
      -
    • -
      -
      Status
      -
      - {% if player.isFighting %} -

      Combat:

      -

      Dungeon: {{ huntingDungeon.name }}

      - {% else %} -

      Combat:

      -

      Dungeon: In protective zone

      - {% endif %} -
    • -
    • - Inventory -
    • - -
    • - Shops -
    • -
    • Mining
    • -
    • Gathering
    • -
    • Harvesting
    • -
    -
    -
    diff --git a/src/UI/Template/minimalistic.template.twig b/src/UI/Template/minimalistic.template.twig index 167dfe8..48dbfec 100644 --- a/src/UI/Template/minimalistic.template.twig +++ b/src/UI/Template/minimalistic.template.twig @@ -80,7 +80,7 @@
    - {% include 'component/player-status.twig' %} + {% include 'component/player-quick-actions.twig' %} From 82b0b5aa4c0719cd8a1f690aadaf6f8f14b7343e Mon Sep 17 00:00:00 2001 From: Temirkhan Date: Tue, 5 Sep 2023 22:00:22 +0200 Subject: [PATCH 27/29] Introduced player states --- src/Player/Player.php | 51 +++++++++++++++++----- src/Server.php | 10 ++--- src/UI/Template/component/player-info.twig | 4 +- updates/20230905192025.sql | 6 +++ 4 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 updates/20230905192025.sql diff --git a/src/Player/Player.php b/src/Player/Player.php index 031bb44..88fc647 100644 --- a/src/Player/Player.php +++ b/src/Player/Player.php @@ -18,6 +18,10 @@ class Player { + public const STATE_IDLE = 0; + public const STATE_IN_COMBAT = 1; + public const STATE_PERFORMING_ACTIVITY = 2; + public const MAX_POSSIBLE_STAMINA = 100; private readonly PlayerLog $logger; @@ -34,12 +38,12 @@ public function __construct( public function isFighting(): bool { - return 1 === (int) $this->getProperty('in_combat'); + return $this->isInState(self::STATE_IN_COMBAT); } public function isInProtectiveZone(): bool { - return !$this->isFighting(); + return $this->isInState(self::STATE_IDLE); } public function isInDungeon(Dungeon $dungeon): bool @@ -68,14 +72,13 @@ public function enterDungeon(Dungeon $dungeon): null|Error return new Error('You are already hunting in a dungeon'); } - $currentActivity = $this->getCurrentActivity(); - if ($currentActivity !== null) { - return new Error(sprintf('Can not go to the dungeon while performing "%s" activity', $currentActivity->getName())); + if (!$this->isInState(self::STATE_IDLE)) { + return new Error('Can not go to the dungeon if not idle'); } $now = DbTimeFactory::createCurrentTimestamp(); $this->connection->execute('INSERT INTO hunting (character_id, dungeon_id, checked_at, last_reward_at) VALUES (?, ?, ?, ?)', [$this->id, $dungeon->id, $now, $now]); - $this->connection->execute('UPDATE players SET in_combat = 1 WHERE id = ?', [$this->id]); + $this->moveToState(self::STATE_IN_COMBAT); return null; } @@ -101,8 +104,12 @@ public function measureDifficulty(Dungeon $dungeon): string public function leaveDungeon(): void { + if (!$this->isInState(self::STATE_IN_COMBAT)) { + return; + } + $this->connection->execute('DELETE from hunting WHERE character_id = ?', [$this->id]); - $this->connection->execute('UPDATE players SET in_combat = 0 WHERE id = ?', [$this->id]); + $this->moveToState(self::STATE_IDLE); } public function getId(): int @@ -173,14 +180,10 @@ public function getActivitySkillLevel(string $activity): int public function startActivity(ActivityInterface $activity): ?Error { - if (!$this->isInProtectiveZone()) { + if (!$this->isInState(self::STATE_IDLE)) { return new Error('Can not go for activities when not in protective zone'); } - if ($this->getCurrentActivity() !== null) { - return new Error('Character is busy with another activity'); - } - $now = DbTimeFactory::createCurrentTimestamp(); $this->connection->execute(' @@ -188,6 +191,8 @@ public function startActivity(ActivityInterface $activity): ?Error VALUE (?, ?, ?, ?, ?, ?) ', [$this->id, $activity->getName(), $activity->getOption(), $now, $now, $now]); + $this->moveToState(self::STATE_PERFORMING_ACTIVITY); + return null; } @@ -208,7 +213,12 @@ public function getCurrentActivity(): ?CharacterActivity public function stopActivity(): void { + if (!$this->isInState(self::STATE_PERFORMING_ACTIVITY)) { + return; + } + $this->connection->execute('DELETE FROM activity WHERE character_id=' . $this->id); + $this->moveToState(self::STATE_IDLE); } public function getLevel(): int @@ -428,6 +438,23 @@ private function getProperty(string $property): string|int|float|null return $result[$property]; } + private function isInState(int $state): bool + { + return (int) $this->getProperty('state') === $state; + } + + private function moveToState(int $state): void + { + // Dummy state-machine. Check transitions before applying new state + if ($state === self::STATE_IN_COMBAT || $state === self::STATE_PERFORMING_ACTIVITY) { + if (!$this->isInState(self::STATE_IDLE)) { + throw new \DomainException('Impossible transition'); + } + } + + $this->connection->execute('UPDATE players SET state = ? WHERE id = ?', [$state, $this->id]); + } + private function getItemQuantity(int $itemId): int { $result = $this->connection->fetchRow("SELECT amount FROM inventory WHERE item_id = $itemId AND character_id = ?", [$this->id]); diff --git a/src/Server.php b/src/Server.php index bcb1006..7a77778 100644 --- a/src/Server.php +++ b/src/Server.php @@ -73,12 +73,12 @@ private function regenerateStamina(): void $this->logs[] = sprintf('%d', min($minutesPassed, Player::MAX_POSSIBLE_STAMINA)); - // TODO Introduce playerState instead of in_combat $this->db->execute( - 'UPDATE players p LEFT JOIN activity a ON a.character_id=p.id SET stamina = LEAST(stamina + ?, ?) WHERE in_combat = 0 AND stamina < ? AND a.id IS NULL', + 'UPDATE players SET stamina = LEAST(stamina + ?, ?) WHERE state = ? AND stamina < ?', [ $minutesPassed, Player::MAX_POSSIBLE_STAMINA, + Player::STATE_IDLE, Player::MAX_POSSIBLE_STAMINA, ] ); @@ -87,7 +87,7 @@ private function regenerateStamina(): void private function stopExhaustedCharacters(): void { - $labourers = $this->db->fetchRows('SELECT p.id, p.name, in_combat, stamina FROM players p INNER JOIN activity a ON p.id = a.character_id WHERE stamina <= 0 AND in_combat = 0'); + $labourers = $this->db->fetchRows('SELECT id, name FROM players WHERE stamina <= 0 AND state = ' . Player::STATE_PERFORMING_ACTIVITY); foreach ($labourers as $row) { $playerNameWithNoStamina = $row['name']; @@ -95,7 +95,7 @@ private function stopExhaustedCharacters(): void $this->logs[] = sprintf('', $playerNameWithNoStamina); } - $hunters = $this->db->fetchRows('SELECT id, name, in_combat, stamina FROM players WHERE stamina <= 0 AND in_combat = 1'); + $hunters = $this->db->fetchRows('SELECT id, name, stamina FROM players WHERE stamina <= 0 AND state = ' . Player::STATE_IN_COMBAT); foreach ($hunters as $row) { $playerNameWithNoStamina = $row['name']; @@ -103,7 +103,7 @@ private function stopExhaustedCharacters(): void $this->logs[] = sprintf('', $playerNameWithNoStamina); } - $this->db->execute('UPDATE players SET in_combat = 0, stamina = 0 WHERE stamina <= 0 AND in_combat = 1'); + $this->db->execute('UPDATE players SET state = ?, stamina = 0 WHERE stamina <= 0 AND state IN (?, ?)', [Player::STATE_IDLE, Player::STATE_IN_COMBAT, Player::STATE_PERFORMING_ACTIVITY]); } private function giveRewards(): void diff --git a/src/UI/Template/component/player-info.twig b/src/UI/Template/component/player-info.twig index 1ade254..6ad9e9e 100644 --- a/src/UI/Template/component/player-info.twig +++ b/src/UI/Template/component/player-info.twig @@ -14,8 +14,10 @@ {% if player.isFighting %}

    Status:

    + {% elseif player.getCurrentActivity %} +

    Status:

    {% else %} -

    Status:

    +

    Status:

    {% endif %}
    diff --git a/updates/20230905192025.sql b/updates/20230905192025.sql new file mode 100644 index 0000000..db2adaf --- /dev/null +++ b/updates/20230905192025.sql @@ -0,0 +1,6 @@ +ALTER TABLE players + ADD state TINYINT(2) UNSIGNED NOT NULL DEFAULT 0 AFTER id; + +UPDATE players SET state=1 WHERE in_combat=1; + +ALTER TABLE players DROP in_combat; From 737abda40ac4c55e6d3a8b8b73bb51a1019be931 Mon Sep 17 00:00:00 2001 From: Temirkhan Date: Tue, 5 Sep 2023 22:07:58 +0200 Subject: [PATCH 28/29] Added perks details output --- src/UI/Template/character-creation.html.twig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/UI/Template/character-creation.html.twig b/src/UI/Template/character-creation.html.twig index ee2ccc7..0be9ccf 100644 --- a/src/UI/Template/character-creation.html.twig +++ b/src/UI/Template/character-creation.html.twig @@ -54,7 +54,12 @@

    Perks

    -

    Not implemented yet

    + {% if race.perks.canCraft %}

    Can craft items

    {% endif %} + {% if race.perks.canMine %}

    Can mine ore

    {% endif %} + {% if race.perks.canGather %}

    Can gather herbs

    {% endif %} + {% if race.perks.canWoodcut %}

    Can chop wood

    {% endif %} + {% if race.perks.canHarvest %}

    Can grow crops and vegetables

    {% endif %} + {% if race.perks.canBrew %}

    Can brew potions and beverages

    {% endif %}
    {% endfor %} From f36fbae702cebf924318045db87be68937fd20fa Mon Sep 17 00:00:00 2001 From: Temirkhan Date: Tue, 5 Sep 2023 22:36:41 +0200 Subject: [PATCH 29/29] Fixed player log reading --- src/Player/PlayerLog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Player/PlayerLog.php b/src/Player/PlayerLog.php index cb0d39e..59816c6 100644 --- a/src/Player/PlayerLog.php +++ b/src/Player/PlayerLog.php @@ -23,7 +23,7 @@ public function add(int $characterId, string $log): void public function readLogs(int $characterId, int $amount): iterable { $logs = $this->db - ->fetchRows('SELECT message FROM log WHERE id=? ORDER BY tid DESC LIMIT ?', [$characterId, $amount]); + ->fetchRows('SELECT message FROM log WHERE character_id=? ORDER BY tid DESC LIMIT ?', [$characterId, $amount]); foreach ($logs as $log) { yield $log['message'];