Skip to content

Commit 5550be1

Browse files
committed
feat: Add monster drops and item detail pages with improved statistics and tiered drop support
- Introduced new pages for item listings, monster drop details, and overall monster drop statistics. - Implemented a drop system overhaul to support tiered pools and named drop groups. - Enhanced item detail pages with accurate gender display and improved layout. - Added a partial view for displaying drop tables with detailed item information. - Updated version to 3.0.1 with changelog reflecting new features and fixes.
1 parent 85a95c7 commit 5550be1

14 files changed

Lines changed: 2258 additions & 38 deletions

File tree

.github/AGENTS.md

Lines changed: 340 additions & 24 deletions
Large diffs are not rendered by default.

app/Filament/Pages/ItemDetail.php

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
namespace App\Filament\Pages;
4+
5+
use App\Helpers\WebmallItemIconHelper;
6+
use BackedEnum;
7+
use Filament\Pages\Page;
8+
use Filament\Support\Icons\Heroicon;
9+
use SilkPanel\SilkroadModels\Models\Account\AbstractItemNameDesc;
10+
use SilkPanel\SilkroadModels\Models\Shard\RefObjCommon;
11+
12+
class ItemDetail extends Page
13+
{
14+
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedArchiveBox;
15+
16+
protected static string|\UnitEnum|null $navigationGroup = 'Silkroad';
17+
18+
protected string $view = 'filament.pages.item-detail';
19+
20+
protected static bool $shouldRegisterNavigation = false;
21+
22+
protected static ?string $slug = 'items/{id}';
23+
24+
public int $id = 0;
25+
26+
public function mount(int $id): void
27+
{
28+
$this->id = $id;
29+
}
30+
31+
public static function getNavigationLabel(): string
32+
{
33+
return 'Item Detail';
34+
}
35+
36+
public function getTitle(): string
37+
{
38+
$item = $this->getItem();
39+
if ($item) {
40+
$name = ($item->NameENG && $item->NameENG !== '0') ? $item->NameENG : $item->CodeName128;
41+
return $name;
42+
}
43+
return 'Item Detail';
44+
}
45+
46+
public function getItem(): ?object
47+
{
48+
$refObj = RefObjCommon::with('getRefObjItem')
49+
->where('TypeID1', 3)
50+
->find($this->id);
51+
52+
if (!$refObj) {
53+
return null;
54+
}
55+
56+
$names = resolve(AbstractItemNameDesc::class)->getItemNames([$refObj->NameStrID128]);
57+
$refObj->NameENG = $names[$refObj->NameStrID128] ?? null;
58+
59+
return $refObj;
60+
}
61+
62+
public static function iconUrl(string $assocFile): string
63+
{
64+
$icon = WebmallItemIconHelper::resolveIcon($assocFile);
65+
return asset('images/silkroad/' . $icon);
66+
}
67+
68+
public static function typeName(int $typeId2, int $typeId3 = 0, int $typeId4 = 0): string
69+
{
70+
return Items::typeName($typeId2, $typeId3, $typeId4);
71+
}
72+
73+
public static function typeColor(int $typeId2, int $typeId3 = 0): string
74+
{
75+
return Items::typeColor($typeId2, $typeId3);
76+
}
77+
78+
public static function soxLabel(string $codeName, int $itemClass): string
79+
{
80+
return Items::soxLabel($codeName, $itemClass);
81+
}
82+
83+
public static function soxColor(string $sealLabel): string
84+
{
85+
return Items::soxColor($sealLabel);
86+
}
87+
88+
public static function rarityLabel(int $rarity): string
89+
{
90+
return Items::rarityLabel($rarity);
91+
}
92+
93+
public static function rarityColor(int $rarity): string
94+
{
95+
return Items::rarityColor($rarity);
96+
}
97+
98+
public static function genderLabel(int $gender): string
99+
{
100+
return match($gender) {
101+
0 => 'Female only',
102+
1 => 'Male only',
103+
default => 'Any',
104+
};
105+
}
106+
107+
public static function countryLabel(int $country): string
108+
{
109+
return match($country) {
110+
1 => 'Chinese',
111+
2 => 'European',
112+
3 => 'Both',
113+
default => 'Unknown',
114+
};
115+
}
116+
}

app/Filament/Pages/Items.php

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
<?php
2+
3+
namespace App\Filament\Pages;
4+
5+
use App\Helpers\WebmallItemIconHelper;
6+
use BackedEnum;
7+
use Filament\Pages\Page;
8+
use Filament\Support\Icons\Heroicon;
9+
use Illuminate\Pagination\LengthAwarePaginator;
10+
use SilkPanel\SilkroadModels\Models\Account\AbstractItemNameDesc;
11+
use SilkPanel\SilkroadModels\Models\Shard\RefObjCommon;
12+
13+
class Items extends Page
14+
{
15+
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedArchiveBox;
16+
17+
protected static string|\UnitEnum|null $navigationGroup = 'Silkroad';
18+
19+
protected static ?int $navigationSort = 12;
20+
21+
protected string $view = 'filament.pages.items';
22+
23+
public string $search = '';
24+
public string $typeFilter = '';
25+
public int $perPage = 50;
26+
public int $currentPage = 1;
27+
28+
public static function getNavigationLabel(): string
29+
{
30+
return __('filament/items.navigation');
31+
}
32+
33+
public function getTitle(): string
34+
{
35+
return __('filament/items.title');
36+
}
37+
38+
public function updatedSearch(): void
39+
{
40+
$this->currentPage = 1;
41+
}
42+
43+
public function updatedTypeFilter(): void
44+
{
45+
$this->currentPage = 1;
46+
}
47+
48+
public function getItems(): LengthAwarePaginator
49+
{
50+
$query = RefObjCommon::where('TypeID1', 3)
51+
->where('Service', 1)
52+
->select([
53+
'ID', 'CodeName128', 'NameStrID128',
54+
'TypeID2', 'TypeID3', 'TypeID4', 'Rarity',
55+
'ReqLevel1', 'Price', 'CashItem',
56+
'AssocFileIcon128',
57+
]);
58+
59+
if ($this->typeFilter !== '') {
60+
[$t2, $t3] = explode(':', $this->typeFilter . ':');
61+
$query->where('TypeID2', (int) $t2);
62+
if ($t3 !== '') {
63+
$query->where('TypeID3', (int) $t3);
64+
}
65+
}
66+
67+
if (strlen($this->search) >= 2) {
68+
$search = $this->search;
69+
$query->where('CodeName128', 'like', "%{$search}%");
70+
}
71+
72+
$query->orderBy('TypeID2')->orderBy('TypeID3')->orderBy('ReqLevel1')->orderBy('ID');
73+
74+
$total = $query->count();
75+
$items = $query
76+
->with('getRefObjItem:ID,ItemClass')
77+
->offset(($this->currentPage - 1) * $this->perPage)
78+
->limit($this->perPage)
79+
->get();
80+
81+
// Resolve English names in bulk
82+
$nameStrIds = $items->pluck('NameStrID128')->filter()->unique()->values()->all();
83+
$names = resolve(AbstractItemNameDesc::class)->getItemNames($nameStrIds);
84+
85+
$items->each(function ($item) use ($names) {
86+
$item->NameENG = $names[$item->NameStrID128] ?? null;
87+
});
88+
89+
return new LengthAwarePaginator(
90+
$items,
91+
$total,
92+
$this->perPage,
93+
$this->currentPage,
94+
['path' => request()->url()]
95+
);
96+
}
97+
98+
public function getStats(): array
99+
{
100+
return [
101+
'total' => RefObjCommon::where('TypeID1', 3)->where('Service', 1)->count(),
102+
'weapons' => RefObjCommon::where('TypeID1', 3)->where('Service', 1)->where('TypeID2', 1)->where('TypeID3', 6)->count(),
103+
'armors' => RefObjCommon::where('TypeID1', 3)->where('Service', 1)->where('TypeID2', 1)->whereIn('TypeID3', [1, 2, 3, 4, 9, 10, 11])->count(),
104+
'accessories' => RefObjCommon::where('TypeID1', 3)->where('Service', 1)->where('TypeID2', 1)->whereIn('TypeID3', [5, 12])->count(),
105+
];
106+
}
107+
108+
public static function iconUrl(string $assocFile): string
109+
{
110+
$icon = WebmallItemIconHelper::resolveIcon($assocFile);
111+
return asset('images/silkroad/' . $icon);
112+
}
113+
114+
/**
115+
* Resolves the display name using config('item.types')[TypeID1][TypeID2][TypeID3][TypeID4].
116+
* Falls back through levels if a specific TypeID4 name isn't available.
117+
*/
118+
public static function typeName(int $typeId2, int $typeId3 = 0, int $typeId4 = 0): string
119+
{
120+
$map = config('item.types.3', []);
121+
122+
$t3map = $map[$typeId2][$typeId3] ?? null;
123+
if ($t3map === null) {
124+
return 'Other';
125+
}
126+
127+
// TypeID4-specific name (e.g. Sword, Blade, Earring, Necklace)
128+
if ($typeId4 > 0 && isset($t3map[$typeId4])) {
129+
return $t3map[$typeId4];
130+
}
131+
132+
// Fall back to first value in the TypeID3 group as the category label
133+
return reset($t3map) ?: 'Other';
134+
}
135+
136+
public static function typeColor(int $typeId2, int $typeId3 = 0): string
137+
{
138+
if ($typeId2 === 1) {
139+
return match($typeId3) {
140+
6 => 'danger', // Weapon
141+
4 => 'warning', // Shield
142+
5, 12 => 'warning', // Jewel
143+
13, 14 => 'success', // Avatar
144+
default => 'primary', // Armor
145+
};
146+
}
147+
148+
return match($typeId2) {
149+
2 => 'success',
150+
3 => 'info',
151+
4 => 'gray',
152+
5 => 'warning',
153+
default => 'gray',
154+
};
155+
}
156+
157+
public static function rarityLabel(int $rarity): string
158+
{
159+
return match($rarity) {
160+
1 => 'Rare',
161+
2 => 'Rare+',
162+
3 => 'Unique',
163+
default => '',
164+
};
165+
}
166+
167+
public static function rarityColor(int $rarity): string
168+
{
169+
return match($rarity) {
170+
1 => 'info',
171+
2 => 'warning',
172+
3 => 'danger',
173+
default => 'gray',
174+
};
175+
}
176+
177+
/**
178+
* Returns the Seal/Sox label based on CodeName128 and ItemClass.
179+
* sox_type config keys are thresholds: ItemClass >= threshold → use that group.
180+
*/
181+
public static function soxLabel(string $codeName, int $itemClass): string
182+
{
183+
$soxTypes = config('item.sox_type', []);
184+
185+
// Check highest threshold first (Nova = 30)
186+
krsort($soxTypes);
187+
188+
foreach ($soxTypes as $threshold => $keywords) {
189+
if ($itemClass >= $threshold) {
190+
foreach ($keywords as $keyword => $label) {
191+
if (str_contains($codeName, (string) $keyword)) {
192+
return $label;
193+
}
194+
}
195+
}
196+
}
197+
198+
return '';
199+
}
200+
201+
public static function soxColor(string $sealLabel): string
202+
{
203+
return match($sealLabel) {
204+
'Seal of Nova' => 'warning',
205+
'Seal of Star' => 'info',
206+
'Seal of Moon' => 'gray',
207+
'Seal of Sun' => 'danger',
208+
default => 'gray',
209+
};
210+
}
211+
212+
public static function getDetailUrl(int $id): string
213+
{
214+
return ItemDetail::getUrl(['id' => $id]);
215+
}
216+
217+
public function previousPage(): void
218+
{
219+
if ($this->currentPage > 1) {
220+
$this->currentPage--;
221+
}
222+
}
223+
224+
public function nextPage(int $lastPage): void
225+
{
226+
if ($this->currentPage < $lastPage) {
227+
$this->currentPage++;
228+
}
229+
}
230+
231+
public function goToPage(int $page): void
232+
{
233+
$this->currentPage = $page;
234+
}
235+
}

0 commit comments

Comments
 (0)