diff --git a/app/Flare/Models/Character.php b/app/Flare/Models/Character.php index 6c564bc8d..0c6669054 100755 --- a/app/Flare/Models/Character.php +++ b/app/Flare/Models/Character.php @@ -211,6 +211,10 @@ public function classSpecialsEquipped() { return $this->hasMany(CharacterClassSpecialtiesEquipped::class); } + public function globalEventParticipation() { + return $this->hasOne(GlobalEventParticipation::class, 'character_id', 'id'); + } + public function getIsAutoBattlingAttribute() { return !is_null(CharacterAutomation::where('character_id', $this->id)->first()); } diff --git a/app/Flare/Models/GlobalEventGoal.php b/app/Flare/Models/GlobalEventGoal.php new file mode 100644 index 000000000..8d5d48586 --- /dev/null +++ b/app/Flare/Models/GlobalEventGoal.php @@ -0,0 +1,63 @@ + 'integer', + 'reward_every_kills' => 'integer', + 'next_reward_at' => 'integer', + 'event_type' => 'integer', + 'item_specialty_type_reward' => 'integer', + 'should_be_unique' => 'boolean', + 'unique_type' => 'integer', + 'should_be_mythic' => 'boolean', + ]; + + protected $appends = [ + 'total_kills', + ]; + + public function globalEventParticipation(): HasMany { + return $this->hasMany(GlobalEventParticipation::class, 'global_event_goal_id', 'id'); + } + + public function eventType(): EventType { + return new EventType($this->event_type); + } + + public function itemSpecialtyType(): itemSpecialtyType { + return new ItemSpecialtyType($this->item_specialty_type_reward); + } + + public function getTotalKillsAttribute(): int { + return $this->globalEventParticipation->sum('current_kills'); + } +} diff --git a/app/Flare/Models/GlobalEventParticipation.php b/app/Flare/Models/GlobalEventParticipation.php new file mode 100644 index 000000000..162b669f3 --- /dev/null +++ b/app/Flare/Models/GlobalEventParticipation.php @@ -0,0 +1,41 @@ + 'integer', + 'character_id' => 'integer', + 'current_kills' => 'integer', + ]; + + public function globalEventGoal(): BelongsTo { + return $this->belongsTo(GlobalEventGoal::class, 'global_event_goal_id', 'id'); + } + + public function character(): BelongsTo { + return $this->belongsTo(Character::class, 'character_id', 'id'); + } +} diff --git a/app/Flare/Values/LocationEffectValue.php b/app/Flare/Values/LocationEffectValue.php index 9395feb2e..c5ca94c72 100755 --- a/app/Flare/Values/LocationEffectValue.php +++ b/app/Flare/Values/LocationEffectValue.php @@ -31,7 +31,7 @@ class LocationEffectValue { self::INCREASE_STATS_BY_FIVE_HUNDRED => '500pts and 5% towards resistances and skills.', self::INCREASE_STATS_BY_ONE_THOUSAND => '1,000pts and 8% towards resistances and skills. ', self::INCREASE_STATS_BY_TWO_THOUSAND => '2,000pts and 10% towards resistances and skills.', - self::INCREASE_STATS_BY_THREE_THOUSAND => '3,000pts and 14$ towards resistances and skills.', + self::INCREASE_STATS_BY_THREE_THOUSAND => '3,000pts and 14% towards resistances and skills.', ]; protected static $integerValues = [ diff --git a/app/Flare/Values/MapNameValue.php b/app/Flare/Values/MapNameValue.php index 5215032b1..69282d12d 100755 --- a/app/Flare/Values/MapNameValue.php +++ b/app/Flare/Values/MapNameValue.php @@ -146,7 +146,7 @@ public function getMapModifers(): array { 'xp_bonus' => 0.50, 'skill_training_bonus' => 0.50, 'drop_chance_bonus' => 0.30, - 'enemy_stat_bonus' => 0.30, + 'enemy_stat_bonus' => 0.35, 'character_attack_reduction' => 0.30, 'required_location_id' => null, ]; diff --git a/app/Game/Battle/Handlers/GlobalEventParticipationHandler.php b/app/Game/Battle/Handlers/GlobalEventParticipationHandler.php new file mode 100644 index 000000000..58402075d --- /dev/null +++ b/app/Game/Battle/Handlers/GlobalEventParticipationHandler.php @@ -0,0 +1,119 @@ +randomAffixGenerator = $randomAffixGenerator; + } + + public function handleGlobalEventParticipation(Character $character, GlobalEventGoal $globalEventGoal) { + if ($globalEventGoal->total_kills >= $globalEventGoal->max_kills) { + return; + } + + $globalEventParticipation = $character->globalEventParticipation; + + if ($globalEventParticipation->isEmpty()) { + $character->globalEventParticipation()->create([ + 'global_event_goal_id' => $globalEventGoal->id, + 'character_id' => $character->id, + 'current_kills' => 1, + ]); + + return; + } + + $character->globalEventParticipation()->update([ + 'current_kills' => $character->globalEventParticipation->current_kills + 1, + ]); + + $globalEventGoal = $globalEventGoal->refresh(); + + if ($globalEventGoal->total_kills >= $globalEventGoal->next_reward_at) { + $newAmount = $globalEventGoal->next_reward_at + $globalEventGoal->reward_every_kills; + + $globalEventGoal->update([ + 'next_reward_at' => $newAmount >= $globalEventGoal->max_kills ? $globalEventGoal->max_kills : $newAmount, + ]); + + $this->rewardCharactersParticipating($globalEventGoal->refresh()); + } + } + + protected function rewardCharactersParticipating(GlobalEventGoal $globalEventGoal) { + Character::whereIn('id', $globalEventGoal->pluck('globalEventParticipation.character_id')->toArray()) + ->chunkById(100, function ($characters) use ($globalEventGoal) { + foreach ($characters as $character) { + $this->rewardForCharacter($character, $globalEventGoal); + } + }); + } + + protected function rewardForCharacter(Character $character, GlobalEventGoal $globalEventGoal) { + $randomAffixGenerator = $this->randomAffixGenerator->setCharacter($character); + + $item = Item::where('specialty_type', $globalEventGoal->item_specialty_type_reward) + ->whereIsNull('item_prefix_id') + ->whereIsNull('item_suffix_id') + ->whereDoesntHave('appliedHolyStacks') + ->inRandomOrder() + ->first(); + + if (is_null($item)) { + return; + } + + if ($globalEventGoal->is_unique) { + $randomAffixGenerator = $this->randomAffixGenerator->setPaidAmount(RandomAffixDetails::LEGENDARY); + + $newItem = $item->duplicate(); + + $newItem->update([ + 'item_prefix_id' => $randomAffixGenerator->generateAffix('prefix')->id, + 'item_suffix_id' => $randomAffixGenerator->generateAffix('suffix')->id, + ]); + + $character->inventory->slots()->create([ + 'inventory_id' => $character->inventory->id, + 'item_id' => $newItem->id, + ]); + + if ($character->isInventoryFull()) { + event(new ServerMessageEvent($character->user, 'Your characters inventory is full. You were rewarded the Event Item either way.')); + } + } + + if ($globalEventGoal->is_mythic) { + $randomAffixGenerator = $this->randomAffixGenerator->setPaidAmount(RandomAffixDetails::MYTHIC); + + $newItem = $item->duplicate(); + + $newItem->update([ + 'item_prefix_id' => $randomAffixGenerator->generateAffix('prefix')->id, + 'item_suffix_id' => $randomAffixGenerator->generateAffix('suffix')->id, + ]); + + $character->inventory->slots()->create([ + 'inventory_id' => $character->inventory->id, + 'item_id' => $newItem->id, + ]); + + event(new ServerMessageEvent($character->user, 'You were rewarded with an item of great power for participating in the current events global goal')); + + if ($character->isInventoryFull()) { + event(new ServerMessageEvent($character->user, 'Your characters inventory is full. You were rewarded the Event Item either way.')); + } + } + } +} diff --git a/app/Game/Battle/Providers/ServiceProvider.php b/app/Game/Battle/Providers/ServiceProvider.php index 8b3dc1c4c..0202e6405 100755 --- a/app/Game/Battle/Providers/ServiceProvider.php +++ b/app/Game/Battle/Providers/ServiceProvider.php @@ -10,6 +10,7 @@ use App\Flare\Builders\Character\CharacterCacheData; use App\Flare\Builders\RandomAffixGenerator; use App\Flare\Builders\RandomItemDropBuilder; +use App\Flare\Models\GlobalEventParticipation; use App\Flare\ServerFight\Monster\BuildMonster; use App\Flare\ServerFight\MonsterPlayerFight; use App\Flare\ServerFight\Pvp\PvpAttack; @@ -54,6 +55,12 @@ public function register() ); }); + $this->app->bind(GlobalEventParticipation::class, function($app) { + return new GlobalEventParticipation( + $app->make(RandomAffixGenerator::class), + ); + }); + $this->app->bind(GoldRush::class, function($app) { return new GoldRush(); }); diff --git a/app/Game/BattleRewardProcessing/Providers/ServiceProvider.php b/app/Game/BattleRewardProcessing/Providers/ServiceProvider.php index e43c423a4..e10029abd 100755 --- a/app/Game/BattleRewardProcessing/Providers/ServiceProvider.php +++ b/app/Game/BattleRewardProcessing/Providers/ServiceProvider.php @@ -2,7 +2,7 @@ namespace App\Game\BattleRewardProcessing\Providers; - +use App\Flare\Models\GlobalEventGoal; use Illuminate\Support\ServiceProvider as ApplicationServiceProvider; use App\Flare\Services\CharacterRewardService; use App\Game\Battle\Handlers\FactionHandler; @@ -12,8 +12,7 @@ use App\Game\Core\Services\GoldRush; use App\Game\Mercenaries\Services\MercenaryService; -class ServiceProvider extends ApplicationServiceProvider -{ +class ServiceProvider extends ApplicationServiceProvider { /** * Register any application services. * @@ -22,15 +21,16 @@ class ServiceProvider extends ApplicationServiceProvider public function register() { - $this->app->bind(BattleRewardService::class, function($app) { + $this->app->bind(BattleRewardService::class, function ($app) { return new BattleRewardService( $app->make(FactionHandler::class), $app->make(CharacterRewardService::class), - $app->make(GoldRush::class) + $app->make(GoldRush::class), + $app->make(GlobalEventGoal::class) ); }); - $this->app->bind(SecondaryRewardService::class, function($app) { + $this->app->bind(SecondaryRewardService::class, function ($app) { return new SecondaryRewardService( $app->make(MercenaryService::class), $app->make(ClassRankService::class), @@ -43,5 +43,6 @@ public function register() { * * @return void */ - public function boot(){} + public function boot() { + } } diff --git a/app/Game/BattleRewardProcessing/Services/BattleRewardService.php b/app/Game/BattleRewardProcessing/Services/BattleRewardService.php index 4ca836090..7b5a7bb57 100644 --- a/app/Game/BattleRewardProcessing/Services/BattleRewardService.php +++ b/app/Game/BattleRewardProcessing/Services/BattleRewardService.php @@ -5,9 +5,13 @@ use App\Flare\Models\GameMap; use App\Flare\Models\Monster; use App\Flare\Models\Character; +use App\Flare\Models\Event; +use App\Flare\Models\GlobalEventGoal; use App\Game\Core\Services\GoldRush; use App\Game\Battle\Handlers\FactionHandler; use App\Flare\Services\CharacterRewardService; +use App\Flare\Values\EventType; +use App\Game\Battle\Handlers\GlobalEventParticipationHandler; use App\Game\Battle\Jobs\BattleItemHandler; class BattleRewardService { @@ -18,11 +22,16 @@ class BattleRewardService { private FactionHandler $factionHandler; private CharacterRewardService $characterRewardService; private GoldRush $goldRush; - - public function __construct(FactionHandler $factionHandler, CharacterRewardService $characterRewardService, GoldRush $goldRush) { - $this->factionHandler = $factionHandler; - $this->characterRewardService = $characterRewardService; - $this->goldRush = $goldRush; + private GlobalEventParticipationHandler $globalEventParticipationHandler; + + public function __construct(FactionHandler $factionHandler, + CharacterRewardService $characterRewardService, + GoldRush $goldRush, + GlobalEventParticipationHandler $globalEventParticipationHandler) { + $this->factionHandler = $factionHandler; + $this->characterRewardService = $characterRewardService; + $this->goldRush = $goldRush; + $this->globalEventParticipationHandler = $globalEventParticipationHandler; } public function setUp(Monster $monster, Character $character): BattleRewardService { @@ -52,12 +61,33 @@ public function handleBaseRewards() { } protected function handleFactionRewards() { - if ($this->gameMap->mapType()->isPurgatory()) { + if ( + $this->gameMap->mapType()->isPurgatory() || + $this->gameMap->mapType()->isTheIcePlane() + ) { return; } - + $this->factionHandler->handleFaction($this->character, $this->monster); $this->character = $this->character->refresh(); } + + protected function handleGlobalEventGoals() { + $event = Event::whereIn('type', [ + EventType::WINTER_EVENT, + ])->first(); + + if (is_null($event)) { + return; + } + + $globalEventGoal = GlobalEventGoal::where('event_type', $event->type)->first(); + + if (is_null($globalEventGoal)) { + return; + } + + $this->globalEventParticipationHandler->handleGlobalEventParticipation($this->character->refresh(), $globalEventGoal->refresh()); + } } diff --git a/app/Game/Core/Handlers/AnnouncementHandler.php b/app/Game/Core/Handlers/AnnouncementHandler.php index fd0b43768..025dd22e5 100755 --- a/app/Game/Core/Handlers/AnnouncementHandler.php +++ b/app/Game/Core/Handlers/AnnouncementHandler.php @@ -23,7 +23,7 @@ protected function buildAnnouncementForType(string $type): void { 'monthly_pvp' => $this->buildMonthlyPVPMessage(), 'weekly_celestial_spawn' => $this->buildWeeklyCelestialMessage(), 'weekly_currency_drop' => $this->buildWeeklyCurrencyDrop(), - 'createAnnouncement' => $this->buildWinterEventMessage(), + 'winter_event' => $this->buildWinterEventMessage(), default => throw new Exception('Cannot determine announcement type'), }; } diff --git a/app/Game/Events/Jobs/IntiateWinterEvent.php b/app/Game/Events/Jobs/InitiateWinterEvent.php similarity index 100% rename from app/Game/Events/Jobs/IntiateWinterEvent.php rename to app/Game/Events/Jobs/InitiateWinterEvent.php diff --git a/app/Game/Kingdoms/Service/KingdomSettleService.php b/app/Game/Kingdoms/Service/KingdomSettleService.php index c825ebe06..44bba7a84 100755 --- a/app/Game/Kingdoms/Service/KingdomSettleService.php +++ b/app/Game/Kingdoms/Service/KingdomSettleService.php @@ -66,6 +66,10 @@ public function settlePreCheck(Character $character, string $kingdomName): array return $this->errorResult('Child, this is not place to be a King or Queen, The Creator would destroy anything you build down here.'); } + if ($character->map->gameMap->mapType()->isTheIcePlane()) { + return $this->errorResult('The Queen of Ice will not allow you to settle here child.'); + } + $kingdom = Kingdom::where('name', $kingdomName)->where('game_map_id', $character->map->game_map_id)->first(); if (!is_null($kingdom)) { diff --git a/app/Game/Maps/Services/TraverseService.php b/app/Game/Maps/Services/TraverseService.php index 9583777fa..d1066be3f 100755 --- a/app/Game/Maps/Services/TraverseService.php +++ b/app/Game/Maps/Services/TraverseService.php @@ -99,7 +99,7 @@ public function canTravel(int $mapId, Character $character): bool { $gameMap = GameMap::find($mapId); if ($gameMap->mapType()->isLabyrinth()) { - $hasItem = $character->inventory->slots->filter(function($slot) { + $hasItem = $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::LABYRINTH; })->all(); @@ -107,7 +107,7 @@ public function canTravel(int $mapId, Character $character): bool { } if ($gameMap->mapType()->isDungeons()) { - $hasItem = $character->inventory->slots->filter(function($slot) { + $hasItem = $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::DUNGEON; })->all(); @@ -115,7 +115,7 @@ public function canTravel(int $mapId, Character $character): bool { } if ($gameMap->mapType()->isShadowPlane()) { - $hasItem = $character->inventory->slots->filter(function($slot) { + $hasItem = $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::SHADOWPLANE; })->all(); @@ -123,15 +123,15 @@ public function canTravel(int $mapId, Character $character): bool { } if ($gameMap->mapType()->isHell()) { - $hasItem = $character->inventory->slots->filter(function($slot) { + $hasItem = $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::HELL; })->all(); return !empty($hasItem); } - if ($gameMap->mapType()->isPurgatory()) { - $hasItem = $character->inventory->slots->filter(function($slot) { + if ($gameMap->mapType()->isPurgatory() || $gameMap->mapType()->isTheIcePlane()) { + $hasItem = $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::PURGATORY; })->all(); @@ -228,13 +228,13 @@ protected function updateKingdomOwnedKingdom(Character $character): void { $x = $character->map->character_position_x; $y = $character->map->character_position_y; - Kingdom::where('x_position',$x) - ->where('y_position', $y) - ->where('character_id', $character->id) - ->where('game_map_id', $mapId) - ->update([ - 'last_walked' => now(), - ]); + Kingdom::where('x_position', $x) + ->where('y_position', $y) + ->where('character_id', $character->id) + ->where('game_map_id', $mapId) + ->update([ + 'last_walked' => now(), + ]); } /** @@ -283,7 +283,8 @@ protected function changeLocation(Character $character, array $cache): Character $x = $cache['x']; $y = $cache['y']; - if (!$this->mapTileValue->canWalkOnWater($character, $character->map->character_position_x, $character->map->character_position_y) || + if ( + !$this->mapTileValue->canWalkOnWater($character, $character->map->character_position_x, $character->map->character_position_y) || !$this->mapTileValue->canWalkOnDeathWater($character, $character->map->character_position_x, $character->map->character_position_y) || !$this->mapTileValue->canWalkOnMagma($character, $character->map->character_position_x, $character->map->character_position_y) || $this->mapTileValue->isPurgatoryWater($this->mapTileValue->getTileColor($character, $character->map->character_position_x, $character->map->character_position_y)) diff --git a/app/Game/Maps/Values/MapTileValue.php b/app/Game/Maps/Values/MapTileValue.php index a87691551..f08496800 100755 --- a/app/Game/Maps/Values/MapTileValue.php +++ b/app/Game/Maps/Values/MapTileValue.php @@ -27,7 +27,7 @@ public function getTileColor(Character $character, int $xPosition, int $yPositio $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; - return $r.$g.$b; + return $r . $g . $b; } /** @@ -103,7 +103,7 @@ public function canWalkOnDeathWater(Character $character, int $x, int $y): bool $color = $this->getTileColor($character, $x, $y); if ($this->isDeathWaterTile((int) $color)) { - return $character->inventory->slots->filter(function($slot) { + return $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::WALK_ON_DEATH_WATER; })->isNotEmpty(); } @@ -125,7 +125,7 @@ public function canWalkOnWater(Character $character, int $x, int $y): bool { $color = $this->getTileColor($character, $x, $y); if ($this->isWaterTile((int) $color)) { - return $character->inventory->slots->filter(function($slot) { + return $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::WALK_ON_WATER; })->isNotEmpty(); } @@ -146,7 +146,7 @@ public function canWalkOnMagma(Character $character, int $x, int $y): bool { $color = $this->getTileColor($character, $x, $y); if ($this->isMagma((int) $color)) { - return $character->inventory->slots->filter(function($slot) { + return $character->inventory->slots->filter(function ($slot) { return $slot->item->effect === ItemEffectsValue::WALK_ON_MAGMA; })->isNotEmpty(); } diff --git a/database/migrations/2023_11_05_093842_global_event_goals.php b/database/migrations/2023_11_05_093842_global_event_goals.php new file mode 100644 index 000000000..fb57a9e20 --- /dev/null +++ b/database/migrations/2023_11_05_093842_global_event_goals.php @@ -0,0 +1,33 @@ +bigIncrements('id'); + $table->bigInteger('max_kills'); + $table->bigInteger('reward_every_kills'); + $table->bigInteger('next_reward_at'); + $table->integer('event_type'); + $table->integer('item_specialty_type_reward'); + $table->boolean('should_be_unique'); + $table->integer('unique_type'); + $table->boolean('should_be_mythic'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void { + Schema::dropIfExists('global_event_goals'); + } +}; diff --git a/database/migrations/2023_11_05_094509_create_global_event_participation.php b/database/migrations/2023_11_05_094509_create_global_event_participation.php new file mode 100644 index 000000000..1b4d7e059 --- /dev/null +++ b/database/migrations/2023_11_05_094509_create_global_event_participation.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->unsignedBigInteger('global_event_goal_id'); + $table->foreign('global_event_goal_id')->on('global_event_goals')->references('id'); + $table->unsignedBigInteger('character_id'); + $table->foreign('character_id')->on('characters')->references('id'); + $table->bigInteger('current_kills'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('global_event_participation'); + } +}; diff --git a/resources/js/game/admin/event-calendar/event-schedule.tsx b/resources/js/game/admin/event-calendar/event-schedule.tsx index 9614f7b09..0c2cbec9e 100755 --- a/resources/js/game/admin/event-calendar/event-schedule.tsx +++ b/resources/js/game/admin/event-calendar/event-schedule.tsx @@ -13,6 +13,7 @@ import EventScheduleState from "./types/event-schedule-state"; import EventView from "../../components/ui/scheduler/event-view"; import PrimaryButton from "../../components/ui/buttons/primary-button"; import GenerateEventType from "./modals/generate-event-type"; +import { DateTime } from "luxon"; export default class EventSchedule extends React.Component< {}, @@ -43,8 +44,12 @@ export default class EventSchedule extends React.Component< this.setState({ raids: result.data.raids, events: result.data.events.map((event: ProcessedEvent) => { - event.start = new Date(event.start); - event.end = new Date(event.end); + event.start = DateTime.fromJSDate(new Date(event.start)) + .toLocal() + .toJSDate(); + event.end = DateTime.fromJSDate(new Date(event.end)) + .toLocal() + .toJSDate(); event.color = event.currently_running ? "#16a34a" : this.color(event.title); diff --git a/resources/js/game/components/ui/scheduler/event-view.tsx b/resources/js/game/components/ui/scheduler/event-view.tsx index 5c66dd929..1e427793a 100755 --- a/resources/js/game/components/ui/scheduler/event-view.tsx +++ b/resources/js/game/components/ui/scheduler/event-view.tsx @@ -10,12 +10,6 @@ export default class EventView extends React.Component { render() { return ( -
-

- All event start/end dates and times are in GMT-6 - Timezone. -

-
{this.props.deleting ? : null}

diff --git a/resources/js/game/game.tsx b/resources/js/game/game.tsx index 55d999943..da4c92abf 100755 --- a/resources/js/game/game.tsx +++ b/resources/js/game/game.tsx @@ -651,10 +651,13 @@ export default class Game extends React.Component { /> {this.state.view_port < 1600 ? ( - + title="{{ !is_null($location) ? 'Edit: ' . nl2br($location->name) : 'Create New Location' }}" buttons="true" + backUrl="{{ !is_null($location) ? route('locations.location', ['location' => $location->id]) : route('locations.list') }}"> + - + @@ -17,23 +17,35 @@

Basic Location Info (Required)

- - - - - - + + + + + + +

Location Details (Optional)

- - - + + +

Quest Item Rewards (Optional)

- +
diff --git a/resources/views/admin/maps/partials/map-details.blade.php b/resources/views/admin/maps/partials/map-details.blade.php index 6c8bb302f..1fff601c8 100755 --- a/resources/views/admin/maps/partials/map-details.blade.php +++ b/resources/views/admin/maps/partials/map-details.blade.php @@ -111,8 +111,12 @@ class="shadow rounded max-w-full h-auto align-middle border-none img-fluid" /> @if ($map->mapType()->isTheIcePlane())
- This plane is only accessible during The Winter Event which runs from December 15th to March 15 - the following year. +

This plane is only accessible during The Winter Event which runs from December + 15th to March 15 + the following year.

+ +

If you have access to Purgatory, you can traverse to this plane from any + plane. Kingdoms cannot be settled here.

The Ice Qaueen Reins!