Skip to content

Commit 3f2491d

Browse files
committed
feat: add gen codes (generate, toGenCode, parseGenCode, genCodeFromLink)
1 parent 3df0190 commit 3f2491d

2 files changed

Lines changed: 613 additions & 0 deletions

File tree

src/GenCode.php

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace VlyDev\Steam;
6+
7+
/**
8+
* Gen code utilities for CS2 inspect links.
9+
*
10+
* Gen codes are space-separated command strings used on community servers:
11+
* !gen {defindex} {paintindex} {paintseed} {paintwear}
12+
* !gen {defindex} {paintindex} {paintseed} {paintwear} {s0_id} {s0_wear} ... {s4_id} {s4_wear} [{kc_id} {kc_wear} ...]
13+
*
14+
* Stickers are always padded to 5 slot pairs. Keychains follow without padding.
15+
*/
16+
final class GenCode
17+
{
18+
public const INSPECT_BASE = 'steam://rungame/730/76561202255233023/+csgo_econ_action_preview%20';
19+
20+
/**
21+
* Format a float value, stripping trailing zeros (max 8 decimal places).
22+
*/
23+
private static function formatFloat(float $value): string
24+
{
25+
$s = rtrim(number_format($value, 8, '.', ''), '0');
26+
$s = rtrim($s, '.');
27+
return $s === '' ? '0' : $s;
28+
}
29+
30+
/**
31+
* Serialize stickers to [id, wear] pairs, optionally padded to N slots.
32+
*
33+
* @param Sticker[] $stickers
34+
* @param int|null $padTo
35+
* @return string[]
36+
*/
37+
private static function serializeStickerPairs(array $stickers, ?int $padTo): array
38+
{
39+
$result = [];
40+
$filtered = array_filter($stickers, fn(Sticker $s) => $s->stickerId !== 0);
41+
42+
if ($padTo !== null) {
43+
$slotMap = [];
44+
foreach ($filtered as $s) {
45+
$slotMap[$s->slot] = $s;
46+
}
47+
for ($slot = 0; $slot < $padTo; $slot++) {
48+
if (isset($slotMap[$slot])) {
49+
$s = $slotMap[$slot];
50+
$result[] = (string) $s->stickerId;
51+
$result[] = self::formatFloat($s->wear ?? 0.0);
52+
} else {
53+
$result[] = '0';
54+
$result[] = '0';
55+
}
56+
}
57+
} else {
58+
usort($filtered, fn(Sticker $a, Sticker $b) => $a->slot <=> $b->slot);
59+
foreach ($filtered as $s) {
60+
$result[] = (string) $s->stickerId;
61+
$result[] = self::formatFloat($s->wear ?? 0.0);
62+
}
63+
}
64+
65+
return $result;
66+
}
67+
68+
/**
69+
* Convert an ItemPreviewData to a gen code string.
70+
*
71+
* @param string $prefix The command prefix, e.g. "!gen" or "!g".
72+
*/
73+
public static function toGenCode(ItemPreviewData $item, string $prefix = '!gen'): string
74+
{
75+
$wearStr = $item->paintwear !== null ? self::formatFloat($item->paintwear) : '0';
76+
$parts = [
77+
(string) $item->defindex,
78+
(string) $item->paintindex,
79+
(string) $item->paintseed,
80+
$wearStr,
81+
];
82+
83+
array_push($parts, ...self::serializeStickerPairs($item->stickers, 5));
84+
array_push($parts, ...self::serializeStickerPairs($item->keychains, null));
85+
86+
$payload = implode(' ', $parts);
87+
return $prefix !== '' ? "{$prefix} {$payload}" : $payload;
88+
}
89+
90+
/**
91+
* Generate a full Steam inspect URL from item parameters.
92+
*
93+
* @param int $defIndex Weapon definition ID (e.g. 7 = AK-47)
94+
* @param int $paintIndex Skin/paint ID
95+
* @param int $paintSeed Pattern index (0-1000)
96+
* @param float $paintWear Float value (0.0-1.0)
97+
* @param int $rarity Item rarity tier
98+
* @param int $quality Item quality (e.g. 9 = StatTrak)
99+
* @param Sticker[] $stickers
100+
* @param Sticker[] $keychains
101+
*/
102+
public static function generate(
103+
int $defIndex,
104+
int $paintIndex,
105+
int $paintSeed,
106+
float $paintWear,
107+
int $rarity = 0,
108+
int $quality = 0,
109+
array $stickers = [],
110+
array $keychains = [],
111+
): string {
112+
$data = new ItemPreviewData(
113+
defindex: $defIndex,
114+
paintindex: $paintIndex,
115+
paintseed: $paintSeed,
116+
paintwear: $paintWear,
117+
rarity: $rarity,
118+
quality: $quality,
119+
stickers: $stickers,
120+
keychains: $keychains,
121+
);
122+
$hex = InspectLink::serialize($data);
123+
return self::INSPECT_BASE . $hex;
124+
}
125+
126+
/**
127+
* Generate a gen code string from an existing CS2 inspect link.
128+
*
129+
* Deserializes the inspect link and converts the item data to gen code format.
130+
*
131+
* @param string $hexOrUrl A hex payload or full steam:// inspect URL.
132+
* @param string $prefix The command prefix, e.g. "!gen" or "!g".
133+
*/
134+
public static function genCodeFromLink(string $hexOrUrl, string $prefix = '!gen'): string
135+
{
136+
$item = InspectLink::deserialize($hexOrUrl);
137+
return self::toGenCode($item, $prefix);
138+
}
139+
140+
/**
141+
* Parse a gen code string into an ItemPreviewData.
142+
*
143+
* Accepts codes like:
144+
* "!gen 7 474 306 0.22540508"
145+
* "7 941 2 0.22540508 0 0 0 0 7203 0 0 0 0 0 36 0"
146+
*
147+
* @throws \InvalidArgumentException If the code has fewer than 4 tokens.
148+
*/
149+
public static function parseGenCode(string $genCode): ItemPreviewData
150+
{
151+
$tokens = preg_split('/\s+/', trim($genCode));
152+
if ($tokens === false) {
153+
$tokens = [];
154+
}
155+
156+
// Skip leading !-prefixed command
157+
if (!empty($tokens) && str_starts_with($tokens[0], '!')) {
158+
array_shift($tokens);
159+
}
160+
161+
if (count($tokens) < 4) {
162+
throw new \InvalidArgumentException(
163+
"Gen code must have at least 4 tokens, got: \"{$genCode}\""
164+
);
165+
}
166+
167+
$defIndex = (int) $tokens[0];
168+
$paintIndex = (int) $tokens[1];
169+
$paintSeed = (int) $tokens[2];
170+
$paintWear = (float) $tokens[3];
171+
$rest = array_slice($tokens, 4);
172+
173+
$stickers = [];
174+
$keychains = [];
175+
176+
if (count($rest) >= 10) {
177+
$stickerTokens = array_slice($rest, 0, 10);
178+
for ($slot = 0; $slot < 5; $slot++) {
179+
$sid = (int) $stickerTokens[$slot * 2];
180+
$wear = (float) $stickerTokens[$slot * 2 + 1];
181+
if ($sid !== 0) {
182+
$stickers[] = new Sticker(slot: $slot, stickerId: $sid, wear: $wear);
183+
}
184+
}
185+
$rest = array_slice($rest, 10);
186+
}
187+
188+
for ($i = 0; $i + 1 < count($rest); $i += 2) {
189+
$sid = (int) $rest[$i];
190+
$wear = (float) $rest[$i + 1];
191+
if ($sid !== 0) {
192+
$keychains[] = new Sticker(slot: (int) ($i / 2), stickerId: $sid, wear: $wear);
193+
}
194+
}
195+
196+
return new ItemPreviewData(
197+
defindex: $defIndex,
198+
paintindex: $paintIndex,
199+
paintseed: $paintSeed,
200+
paintwear: $paintWear,
201+
stickers: $stickers,
202+
keychains: $keychains,
203+
);
204+
}
205+
}

0 commit comments

Comments
 (0)