From 0c6183c35c77e94e09e87ae0f94cd3116cf20589 Mon Sep 17 00:00:00 2001 From: Adam Balan Date: Thu, 23 Nov 2023 23:04:14 -0700 Subject: [PATCH] Dependenices, failing test and various other fixes and changes. --- app/Flare/ServerFight/BattleBase.php | 4 +- .../CharacterAttacks/CharacterAttack.php | 1 + .../Fight/CharacterAttacks/Types/CastType.php | 32 ++- .../CharacterAttacks/Types/WeaponType.php | 28 +- .../Controllers/Api/RaidBattleController.php | 11 +- .../Battle/Handlers/BattleEventHandler.php | 1 + .../Battle/Services/RaidBattleService.php | 3 + .../Api/CharacterInventoryController.php | 29 ++ .../EquipBestItemForSlotsTypesService.php | 256 ++++++++++++++++++ .../Console/Commands/EndScheduledEvent.php | 4 + app/Game/Quests/Events/UpdateQuests.php | 7 +- composer.lock | 12 +- .../Admin Section/information.json | 2 +- .../types/tabs/inventory-tab-section-state.ts | 2 + .../event-listeners/game/quest-listeners.ts | 7 +- .../components/tabs/inventory-tab-section.tsx | 49 +++- .../sections/components/quests/quests.tsx | 12 - .../components/raid-fight.tsx | 24 +- .../components/raid-section.tsx | 10 + .../components/types/raid-fight-props.ts | 4 +- .../components/types/raid-selection-state.ts | 1 + .../guide-quests/modals/guide-quest.tsx | 2 - routes/game/character-inventory/api.php | 1 + yarn.lock | 12 +- 24 files changed, 453 insertions(+), 61 deletions(-) create mode 100644 app/Game/CharacterInventory/Services/EquipBestItemForSlotsTypesService.php diff --git a/app/Flare/ServerFight/BattleBase.php b/app/Flare/ServerFight/BattleBase.php index 90def20a0..e6dd9c2d2 100755 --- a/app/Flare/ServerFight/BattleBase.php +++ b/app/Flare/ServerFight/BattleBase.php @@ -128,7 +128,7 @@ protected function elementalAttack(Character $character, ServerMonster $monster) $elementalAttack->setCharacterHealth($this->characterHealth); $characterElementalData = $this->characterCacheData->getCachedCharacterData($character, 'elemental_atonement')['elemental_data']; - + $weaponDamage = $this->characterCacheData->getCachedCharacterData($character, 'weapon_attack'); $elementalAttack->doElementalAttack($monster->getElementData(), $characterElementalData, $weaponDamage); @@ -164,7 +164,7 @@ protected function doMonsterCounter(Character $character, ServerMonster $monster if ($this->getMonsterHealth() < 0) { return; } - + $counter = resolve(Counter::class); $counter->setCharacterHealth($this->characterHealth); diff --git a/app/Flare/ServerFight/Fight/CharacterAttacks/CharacterAttack.php b/app/Flare/ServerFight/Fight/CharacterAttacks/CharacterAttack.php index 6838ab962..badb5bb38 100755 --- a/app/Flare/ServerFight/Fight/CharacterAttacks/CharacterAttack.php +++ b/app/Flare/ServerFight/Fight/CharacterAttacks/CharacterAttack.php @@ -49,6 +49,7 @@ public function attack(Character $character, ServerMonster $monster, bool $isPla $this->weaponType->setCharacterHealth($characterHealth); $this->weaponType->setMonsterHealth($monsterHealth); $this->weaponType->setCharacterAttackData($character, $isPlayerVoided); + $this->weaponType->setAllowEntrancing(true); $this->weaponType->doWeaponAttack($character, $monster); $this->type = $this->weaponType; diff --git a/app/Flare/ServerFight/Fight/CharacterAttacks/Types/CastType.php b/app/Flare/ServerFight/Fight/CharacterAttacks/Types/CastType.php index 5ba655a06..74c53e0c0 100755 --- a/app/Flare/ServerFight/Fight/CharacterAttacks/Types/CastType.php +++ b/app/Flare/ServerFight/Fight/CharacterAttacks/Types/CastType.php @@ -19,6 +19,8 @@ class CastType extends BattleBase private SpecialAttacks $specialAttacks; + private bool $allowEntrancing = false; + public function __construct(CharacterCacheData $characterCacheData, Entrance $entrance, CanHit $canHit, SpecialAttacks $specialAttacks) { parent::__construct($characterCacheData); @@ -44,6 +46,12 @@ public function setCharacterCastAndAttack(Character $character, bool $isVoided): return $this; } + public function setAllowEntrancing(bool $allow): CastType { + $this->allowEntrancing = $allow; + + return $this; + } + public function resetMessages() { $this->clearMessages(); $this->entrance->clearMessages(); @@ -120,20 +128,22 @@ public function castAttack(Character $character, ServerMonster $monster) { $this->elementalAttack($character, $monster); } - return; + return $this; } - if (!$this->isEnemyEntranced) { + if (!$this->isEnemyEntranced && $this->allowEntrancing) { + $this->doEnemyEntrance($character, $monster, $this->entrance); + } - //$this->doEnemyEntrance($character, $monster, $this->entrance); + if ($this->isEnemyEntranced) { + $this->doSpellDamage($character, $monster, $spellDamage, true); - if ($this->isEnemyEntranced) { - $this->doSpellDamage($character, $monster, $spellDamage, true); - return $this; + if ($this->allowSecondaryAttacks) { + $this->secondaryAttack($character, $monster); + + $this->elementalAttack($character, $monster); } - } else if ($this->isEnemyEntranced) { - $this->doSpellDamage($character, $monster, $spellDamage, true); return $this; } @@ -142,6 +152,12 @@ public function castAttack(Character $character, ServerMonster $monster) { $this->doSpellDamage($character, $monster, $spellDamage, true); + if ($this->allowSecondaryAttacks) { + $this->secondaryAttack($character, $monster); + + $this->elementalAttack($character, $monster); + } + return $this; } diff --git a/app/Flare/ServerFight/Fight/CharacterAttacks/Types/WeaponType.php b/app/Flare/ServerFight/Fight/CharacterAttacks/Types/WeaponType.php index 403c00239..cc1d45f6f 100755 --- a/app/Flare/ServerFight/Fight/CharacterAttacks/Types/WeaponType.php +++ b/app/Flare/ServerFight/Fight/CharacterAttacks/Types/WeaponType.php @@ -9,6 +9,7 @@ use App\Flare\ServerFight\Fight\CharacterAttacks\SpecialAttacks; use App\Flare\ServerFight\Fight\Entrance; use App\Flare\ServerFight\Monster\ServerMonster; +use App\Flare\Values\WeaponTypes; class WeaponType extends BattleBase { @@ -18,6 +19,8 @@ class WeaponType extends BattleBase { private SpecialAttacks $specialAttacks; + private bool $canEntrance = false; + public function __construct(CharacterCacheData $characterCacheData, Entrance $entrance, CanHit $canHit, SpecialAttacks $specialAttacks) { parent::__construct($characterCacheData); @@ -34,6 +37,12 @@ public function setCharacterAttackData(Character $character, bool $isVoided): We return $this; } + public function setAllowEntrancing(bool $allowEntrance): WeaponType { + $this->canEntrance = $allowEntrance; + + return $this; + } + public function doPvpWeaponAttack(Character $attacker, Character $defender): WeaponType { $weaponDamage = $this->attackData['weapon_damage']; @@ -78,8 +87,19 @@ public function doPvpWeaponAttack(Character $attacker, Character $defender): Wea public function doWeaponAttack(Character $character, ServerMonster $serverMonster): WeaponType { $weaponDamage = $this->attackData['weapon_damage']; + if (!$this->isEnemyEntranced && $this->canEntrance) { + $this->doEnemyEntrance($character, $serverMonster, $this->entrance); + } + if ($this->isEnemyEntranced) { $this->weaponAttack($character, $serverMonster, $weaponDamage); + + if ($this->allowSecondaryAttacks && !$this->abortCharacterIsDead) { + $this->secondaryAttack($character, $serverMonster); + + $this->elementalAttack($character, $serverMonster); + } + return $this; } @@ -88,6 +108,12 @@ public function doWeaponAttack(Character $character, ServerMonster $serverMonste $this->weaponAttack($character, $serverMonster, $weaponDamage); + if ($this->allowSecondaryAttacks && !$this->abortCharacterIsDead) { + $this->secondaryAttack($character, $serverMonster); + + $this->elementalAttack($character, $serverMonster); + } + return $this; } @@ -101,7 +127,7 @@ public function doWeaponAttack(Character $character, ServerMonster $serverMonste } else { $this->addMessage('Your attack missed!', 'enemy-action'); - if ($this->allowSecondaryAttacks) { + if ($this->allowSecondaryAttacks && !$this->abortCharacterIsDead) { $this->secondaryAttack($character, $serverMonster); $this->elementalAttack($character, $serverMonster); diff --git a/app/Game/Battle/Controllers/Api/RaidBattleController.php b/app/Game/Battle/Controllers/Api/RaidBattleController.php index 5ded26cbe..28af3e604 100755 --- a/app/Game/Battle/Controllers/Api/RaidBattleController.php +++ b/app/Game/Battle/Controllers/Api/RaidBattleController.php @@ -6,6 +6,7 @@ use App\Flare\Models\RaidBoss; use App\Flare\Models\Character; use App\Flare\Models\RaidBossParticipation; +use App\Game\Battle\Events\AttackTimeOutEvent; use App\Game\Battle\Request\AttackTypeRequest; use App\Game\Battle\Services\Concerns\HandleCachedRaidCritterHealth; use Illuminate\Http\JsonResponse; @@ -79,12 +80,16 @@ public function fetchRaidMonster(Character $character, Monster $monster): JsonRe public function fightMonster(AttackTypeRequest $attackTypeRequest, Character $character, Monster $monster): JsonResponse { if ($monster->is_raid_monster) { - $result = $this->raidBattleService->fightRaidMonster($character, $monster->id, $attackTypeRequest->attack_type, false); + $result = $this->raidBattleService->fightRaidMonster($character, $monster->id, $attackTypeRequest->attack_type); $status = $result['status']; unset($result['status']); + if ($result['monster_current_health'] <= 0) { + event(new AttackTimeOutEvent($character)); + } + return response()->json($result, $status); } @@ -111,6 +116,10 @@ public function fightMonster(AttackTypeRequest $attackTypeRequest, Character $ch unset($result['status']); + if ($result['monster_current_health'] <= 0) { + event(new AttackTimeOutEvent($character)); + } + return response()->json($result, $status); } } diff --git a/app/Game/Battle/Handlers/BattleEventHandler.php b/app/Game/Battle/Handlers/BattleEventHandler.php index 6dcc5e06f..f5cf9c100 100755 --- a/app/Game/Battle/Handlers/BattleEventHandler.php +++ b/app/Game/Battle/Handlers/BattleEventHandler.php @@ -65,6 +65,7 @@ public function processDeadCharacter(Character $character): void { */ public function processMonsterDeath(int $characterId, int $monsterId): void { $monster = Monster::find($monsterId); + $character = Character::find($characterId); if (is_null($monster)) { diff --git a/app/Game/Battle/Services/RaidBattleService.php b/app/Game/Battle/Services/RaidBattleService.php index 9b3c46f6d..4ede75d01 100755 --- a/app/Game/Battle/Services/RaidBattleService.php +++ b/app/Game/Battle/Services/RaidBattleService.php @@ -16,6 +16,7 @@ use App\Game\Battle\Events\UpdateRaidBossHealth; use App\Game\Battle\Handlers\BattleEventHandler; use App\Game\Battle\Services\Concerns\HandleCachedRaidCritterHealth; +use App\Game\BattleRewardProcessing\Jobs\BattleAttackHandler; use App\Game\BattleRewardProcessing\Jobs\RaidBossRewardHandler; use App\Game\Core\Traits\ResponseBuilder; use Exception; @@ -234,6 +235,8 @@ public function fightRaidMonster(Character $character, int $monsterId, string $a $this->deleteMonsterCacheHealth($character->id, $monsterId); + BattleAttackHandler::dispatch($character->id, $monsterId); + return $this->successResult($resultData); } diff --git a/app/Game/CharacterInventory/Controllers/Api/CharacterInventoryController.php b/app/Game/CharacterInventory/Controllers/Api/CharacterInventoryController.php index c8f391e63..0294c6b77 100755 --- a/app/Game/CharacterInventory/Controllers/Api/CharacterInventoryController.php +++ b/app/Game/CharacterInventory/Controllers/Api/CharacterInventoryController.php @@ -2,6 +2,7 @@ namespace App\Game\CharacterInventory\Controllers\Api; +use App\Game\CharacterInventory\Services\EquipBestItemForSlotsTypesService; use Exception; use League\Fractal\Manager; use League\Fractal\Resource\Item as FractalItem; @@ -472,6 +473,34 @@ public function equipItem(EquipItemValidation $request, Character $character, Eq } } + public function equipBestInSlot(Character $character, EquipBestItemForSlotsTypesService $equipBestItemForSlotsTypesService): JsonResponse { + try { + + $changedEquipment = $equipBestItemForSlotsTypesService->compareAndEquipBestItems($character); + + $character = $character->refresh(); + + $this->updateCharacterAttackDataCache($character); + + $characterInventoryService = $this->characterInventoryService->setCharacter($character); + + $message = $changedEquipment ? 'Equipped or Replaced equipped items with the best in slot items!' : 'You currently have the best items equipped!'; + + return response()->json([ + 'inventory' => [ + 'inventory' => $characterInventoryService->fetchCharacterInventory(), + 'equipped' => $characterInventoryService->fetchEquipped(), + 'sets' => $characterInventoryService->getCharacterInventorySets(), + ], + 'message' => $message + ]); + } catch (Exception $e) { + return response()->json([ + 'message' => $e->getMessage() + ], 422); + } + } + /** * @param Request $request * @param Character $character diff --git a/app/Game/CharacterInventory/Services/EquipBestItemForSlotsTypesService.php b/app/Game/CharacterInventory/Services/EquipBestItemForSlotsTypesService.php new file mode 100644 index 000000000..028708c6e --- /dev/null +++ b/app/Game/CharacterInventory/Services/EquipBestItemForSlotsTypesService.php @@ -0,0 +1,256 @@ +equipItemService = $equipItemService; + } + + public function compareAndEquipBestItems(Character $character): bool { + $inventorySlots = $character->inventory->slots->where('equipped', false)->whereNotIn('item.type', ['alchemy', 'quest']); + + if ($inventorySlots->isEmpty()) { + return false; + } + + $sortedSlots = $this->getsItemsToCompare($inventorySlots); + + if (empty($sortedSlots)) { + return false; + } + + $bestItemsToPossiblyEquip = $this->getBestItemsInSortedInventory($sortedSlots); + + if (empty($bestItemsToPossiblyEquip)) { + return false; + } + + $equippedItems = $this->fetchEquipped($character); + + $itemsToEquipOrReplace = $this->getItemsToReplaceBasedOnInventory($bestItemsToPossiblyEquip, $equippedItems); + + if (empty($itemsToEquipOrReplace)) { + return false; + } + + dump($itemsToEquipOrReplace); + } + + protected function getsItemsToCompare(Collection $inventorySlots): array { + $itemsToCompare = []; + + foreach (self::TYPES as $type) { + $itemsForType = $inventorySlots->where('item.type', $type); + + if ($itemsForType->isNotEmpty()) { + $itemsToCompare[$type] = $itemsForType; + } + } + + return $itemsToCompare; + } + + protected function getBestItemsInSortedInventory(array $sortedInventory): array { + $bestItemsToPossiblyEquip = []; + + foreach ($sortedInventory as $type => $slots) { + $bestForType = $this->getBestItem($slots); + + if (is_null($bestForType)) { + continue; + } + + if (!$this->validateOnlyOneUniqueOrMythic($bestForType, $bestItemsToPossiblyEquip) || + !$this->validateOnlyOneTrinket($bestForType, $bestItemsToPossiblyEquip) || + !$this->validateOnlyOneArtifact($bestForType, $bestItemsToPossiblyEquip)) + { + continue; + } + + $bestItemsToPossiblyEquip[$type] = $bestForType; + } + + return $bestItemsToPossiblyEquip; + } + + protected function getBestItem(Collection $slots): InventorySlot | null { + return $slots->sortByDesc(function ($slot) { + return array_reduce(self::CORE_ATTRIBUTES, function ($carry, $attribute) use ($slot) { + return $carry + $slot->item->{$attribute}; + }, 0); + })->first(); + } + + protected function getItemsToReplaceBasedOnInventory(array $bestItemsToEquip, ?Collection $equipped = null): array { + $itemsToReplaceOrEquip = []; + + if (is_null($equipped)) { + foreach ($bestItemsToEquip as $type => $inventorySlot) { + $itemsToReplaceOrEquip[$type] = [ + 'slot_id' => $inventorySlot->id, + 'position' => '', // TODO: Fix!!! + 'equip_type' => $inventorySlot->item->type, + ]; + } + + return $itemsToReplaceOrEquip; + } + + foreach ($bestItemsToEquip as $type => $inventorySlot) { + + $equippedItem = $equipped->where('item.type', $type)->first(); + + if (is_null($equipped)) { + $itemsToReplaceOrEquip[$type] = $inventorySlot; + } + + $bestItem = $this->fetchBestItem($inventorySlot, $equippedItem); + + if (is_null($bestItem)) { + continue; + } + + $itemsToReplaceOrEquip[$type] = [ + 'slot_id' => $inventorySlot->id, + 'position' => $equippedItem->position, + 'equip_type' => $inventorySlot->type, + ]; + } + + return $itemsToReplaceOrEquip; + } + + protected function fetchBestItem(InventorySlot $itemToEquip, InventorySlot|SetSlot $equippedItem): InventorySlot | null { + $equippedScore = array_reduce(self::CORE_ATTRIBUTES, function ($carry, $attribute) use ($equippedItem) { + return $carry + $equippedItem->{$attribute}; + }, 0); + + $bestItemScore = array_reduce(self::CORE_ATTRIBUTES, function ($carry, $attribute) use ($itemToEquip) { + return $carry + $itemToEquip->{$attribute}; + }, 0); + + return ($equippedScore >= $bestItemScore) ? null : $itemToEquip; + } + + private function validateOnlyOneUniqueOrMythic(InventorySlot $bestItem, array $existingItems): bool { + + if (!$bestItem->item->is_mythic && !$bestItem->item->is_unique) { + return true; + } + + foreach ($existingItems as $type => $slot) { + if ($slot->item->is_mythic || $slot->item->is_unique) { + return false; + } + } + + return true; + } + + private function validateOnlyOneTrinket(InventorySlot $bestItem, array $existingItems): bool { + + if ($bestItem->item->type !== 'trinket') { + return true; + } + + foreach ($existingItems as $type => $slot) { + if ($slot->item->type === 'trinket') { + return false; + } + } + + return true; + } + + private function validateOnlyOneArtifact(InventorySlot $bestItem, array $existingItems): bool { + + if ($bestItem->item->type !== 'artifact') { + return true; + } + + foreach ($existingItems as $type => $slot) { + if ($slot->item->type === 'artifact') { + return false; + } + } + + return true; + } +} diff --git a/app/Game/Events/Console/Commands/EndScheduledEvent.php b/app/Game/Events/Console/Commands/EndScheduledEvent.php index 212ea2ba4..e32082022 100644 --- a/app/Game/Events/Console/Commands/EndScheduledEvent.php +++ b/app/Game/Events/Console/Commands/EndScheduledEvent.php @@ -267,6 +267,10 @@ protected function endWinterEvent(KingdomEventService $kingdomEventService, ExplorationAutomationService $explorationAutomationService) { $event = Event::where('type', EventType::WINTER_EVENT)->first(); + if (is_null($event)) { + return; + } + $kingdomEventService->handleKingdomRewardsForEvent(MapNameValue::ICE_PLANE); $gameMap = GameMap::where('name', MapNameValue::ICE_PLANE)->first(); diff --git a/app/Game/Quests/Events/UpdateQuests.php b/app/Game/Quests/Events/UpdateQuests.php index 60b561181..778314771 100755 --- a/app/Game/Quests/Events/UpdateQuests.php +++ b/app/Game/Quests/Events/UpdateQuests.php @@ -2,11 +2,12 @@ namespace App\Game\Quests\Events; +use App\Flare\Models\Event; use App\Flare\Models\User; +use App\Game\Events\Values\EventType; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; -use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -16,11 +17,15 @@ class UpdateQuests implements ShouldBroadcastNow { public array $quests; + public bool $isWinterEvent; + /** * Constructor */ public function __construct(array $quests) { $this->quests = $quests; + + $this->isWinterEvent = Event::where('type', EventType::WINTER_EVENT)->count() > 0; } /** diff --git a/composer.lock b/composer.lock index 6dea1a331..0909b309e 100644 --- a/composer.lock +++ b/composer.lock @@ -10600,16 +10600,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.8", + "version": "10.1.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "84838eed9ded511f61dc3e8b5944a52d9017b297" + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/84838eed9ded511f61dc3e8b5944a52d9017b297", - "reference": "84838eed9ded511f61dc3e8b5944a52d9017b297", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a56a9ab2f680246adcf3db43f38ddf1765774735", + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735", "shasum": "" }, "require": { @@ -10666,7 +10666,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.8" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.9" }, "funding": [ { @@ -10674,7 +10674,7 @@ "type": "github" } ], - "time": "2023-11-15T13:31:15+00:00" + "time": "2023-11-23T12:23:20+00:00" }, { "name": "phpunit/php-file-iterator", diff --git a/resources/data-imports/Admin Section/information.json b/resources/data-imports/Admin Section/information.json index 437da1fde..1a3e2b85f 100644 --- a/resources/data-imports/Admin Section/information.json +++ b/resources/data-imports/Admin Section/information.json @@ -1,3 +1,3 @@ -[{"id":1,"page_name":"rules","page_sections":[{"order":"1","content":"

The rules are simple and straightforward. You can either follow them or suffer the consequences.<\/p>

There are two forms of punishment in the game. One is being silenced, and the other is being banned.<\/p>

A player can be silenced for 10, 20 or 30 minutes. If the player is not online, and they will be emailed that they can speak again (assuming they have those settings enabled).<\/p>

Players can be silenced via The Creator<\/strong> or through chat throttling (see below).<\/p>

A player can be banned for 1 day, 1 week or \u201cFor ever\u201d. A banned player may request to be unbanned via the \u201cbanned unfairly?\u201d form that can be accessed via the login screen. However, they can only make this request if they have been banned forever<\/strong>.<\/p>

A player will be emailed if they have been banned and when (or if) the ban lifts. You cannot turn these emails off. You will also always be given a reason as to why you were banned.<\/p>

Once the player fills out the form, any future requests will be ignored by the system. You will receive an email, regarding your request one way or the other.<\/p>

All decisions are final<\/strong>.<\/p>

With that out of the way let's go over some rules of Tlessa.<\/p>

Core Rules<\/h2>
  1. There is to be no RMT (Real Money Trade) of any kind in-game. This includes but not limited to: Items, characters, kingdoms, playing your character, managing your kingdoms.<\/li>
  2. Taking advantage of game bugs, including accessing parts of the game you are not allowed to, and failure to report said bugs can and will get you banned.<\/li>
  3. Hacking, Cheating, Scripting bots to play for you will also get you banned.<\/li>
  4. Third party tools of any kind will also get you banned.<\/li>
  5. Accessing other characters information and not reporting it as a security flaw will get you banned.<\/li>
  6. No racial slurs or other attacks on anyone for any reason other than playful banter in-game (see Free speech above)<\/li><\/ol>

    Being Banned and Multiple Accounts<\/h2>

    Tlessa will let you have 10 accounts. DO NOT ASK FOR MORE<\/strong>. The emails must be unique. This game doesn't really support tunes, as you can't trade between players without using the market board.<\/p>

    Characters may log into multiple accounts, but may only send one request per client to the server at a time. This is called Multi Boxing.<\/p>

    Players may not use scripts or third party tools to manage multiple accounts.<\/p>

    Should one of the 10 characters be banned, even for a day or a week or god forbid, forever - ALL CHARACTERS<\/strong> will be banned. So if you and your house mate are playing, and they get banned, you just lost your account.<\/p>

    You can still do the \"banned unfairly\" request and I, The Creator will review the request.<\/p>","content_image_path":null,"live_wire_component":null},{"order":2,"content":"

    Chat<\/h2>

    Since chat is a part of the game, The Creator, will never moderate chat unless it is in the best interest of the community.<\/p>

    Chat messages, including private messages are all logged, unencrypted and can be seen at any time (going back 90 days). Should the need arise The Creator can and will step in to deal the appropriate punishment.<\/p>

    Racial slurs, attacks on other people for any reason including but not limited to<\/strong>: Sexual Identity\/Preference, Racial Heritage, Age, Sex, Religious Affiliation or Disability. Will not be tolerated and will result in the appropriate punishment.<\/p>

    Do not post your personal information in chat<\/strong>. This includes but not limited to<\/strong>: Email, address, real name, phone number, credit card or any other identifiable information about you.<\/p>

    With that said your location in-game is shown in chat as X,Y coordinates.<\/p>

    Last but not least, no auction in chat. If you want to sell something go to the market board<\/a>. Failure to head this rule can result in being silenced and then, for repeat offenders, banned.<\/p>

    Chat Throttling<\/h2>

    A player may only send 25 messages every 1 minutes. Failure to heed this, and the system will auto silence you for 5 minutes. If you are not logged in when you can speak again, and have the email settings enabled, you will be emailed when you can speak again.<\/p>

    \"Free\" Speech<\/h2>

    Just because chat is self moderated for the most part, does not mean The Creator will allow for hate speech, racism or wild conspiracies or the posting of \"fake news\". These are bannable offenses and will result in a silence, followed by an appropriate ban for repeatable offenses.<\/p>

    The Creator suggests, you leave religion and politics out of the general chat discussion. While I will not go and silence or ban someone for talking about current world events, if things do get out of hand appropriate measures will be taken.<\/p>","content_image_path":null,"live_wire_component":null},{"order":3,"content":"

    Character Names<\/h2>
    1. Character names may not have special characters in them (including emojis or asci characters).<\/li>
    2. Character names may not have spaces<\/li>
    3. Character names must be between 5 and 15 characters long.<\/li>
    4. Character names may only container letters of any case and numbers.<\/li><\/ol>

      If a character name is deemed to be offensive, that is it contains a racial slur or some other offensive slur, The Creator<\/strong> can and will initiate a forced name change.<\/p>

      Any attempt to circumvent changing your name including scripts, third party tools or hacks will get your account banned forever.<\/p>

      Once this popup shows up, even if you log out and back in or refresh the page, this popup will stay.<\/p>","content_image_path":"rules\/Y3jPo64QwHOSeS8kT5bdRhzMREXitCB4QxOZT37r.png","live_wire_component":null}],"created_at":"2023-10-19T18:42:23.000000Z","updated_at":"2023-10-19T18:42:23.000000Z"},{"id":2,"page_name":"home","page_sections":[{"order":"1","content":"

      Welcome to Tlessa, a full-fledged, completely free, no cash shops, no pay to win PBBG that allows you to level, gear, craft, enchant, settle kingdoms and so much more.<\/p>

      The store of Tlessa is simple: The Creator has escaped from Purgatory, follow a series of Quests starting on surface to learn more about the NPC\u2019s and the Creator himself. Help to answer questions, unravel the mystery of how and why The Creator escaped from Purgatory.<\/p>

      Tlessa has a lot of systems and a lot to do, most of which is gated behind quests. As a result, the game can be a bit confusing to figure out. As a result, players who signup can enable The Guide, who\u2019s is a quest giver to help guide you through your first thousand levels.<\/p>

      That\u2019s right, there are well over a thousand levels, but unlock additional character levels, you must complete quests, level your character, earn currencies and progress through the world. Are you ready?<\/p>

       Key Features<\/h2>