server: unlimited teleport ring 70058, sold in a two-page item mall

ring-warp/: ring_warp.quest without use count or cooldown (the ring is never
used up) + install.sh (GBK, make.py, item name 移动戒指).
npc-shops: [page] lines in items.tsv make an extended shop written to
shop_table_ex.txt; item-mall is now 增益 (29) + 道具 (12, incl. 70058 at 1 gold).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-30 08:18:56 +09:00
co-authored by Claude Opus 5.5
parent 000d4841fd
commit 42e4cf1a9f
7 changed files with 177 additions and 12 deletions
+7 -2
View File
@@ -6,10 +6,14 @@
可选的 `NPC_NAME`(NPC 头顶显示的名字,UTF-8)。
- `items.tsv`:vnum、数量、价格、名称,一行一件。最多 40 件(`SHOP_HOST_ITEM_MAX_NUM`,5×8 格)。
客户端按 vnum 从小到大排列。
- 多页商店:`items.tsv` 里的 `[页名]` 行开始新的一页(最多 3 页,每页最多 39 件,按文件顺序摆放),
`shop.conf` 的 `TAB_VNUMS` 给第 2、3 页的商店 vnum。这种商店不走 DB 的 `shop` / `shop_item`,
而是写进 `share/locale/newcibn/shop_table_ex.txt`(game 的 `CShopManager::ReadShopTableEx`,价格就在文件里);
同一个 NPC 不能同时有 DB 商店,所以 install.sh 会删掉 DB 里对应的行。目前这个文件只归 `item-mall` 用。
| 目录 | 商店 | NPC | 位置 |
|---|---|---|---|
| `item-mall/` | 14 `item_mall` | 9010 百店老板娘 | 杂货店 9003 旁 |
| `item-mall/` | 14 / 16 `item_mall`(两页:增益、道具) | 9010 百店老板娘 | 杂货店 9003 旁 |
| `mount/` | 15 `mount` | 20368 坐骑商人(原「铁算盘」,未使用的货郎模型) | 马夫 20349 旁 |
`install.sh` 对一个商店做这些事:
@@ -25,7 +29,7 @@
系统菜单里的「商城」按钮发 `/in_game_mall`,服务端回 `mall <url>`。203 没有网页商城,所以把商城物品放进 9010 的商店。
客户端的 PORT 模块 `mt_mallhint` 会把这个按钮改成提示玩家去找百店老板娘。`item_mall.quest` 只在测试服下给 9010
加对话选项,正式服不受影响。这 40 件物品的 `shop_buy_price` 是 0,卖回给 NPC 拿不到钱。
加对话选项,正式服不受影响。这些物品的 `shop_buy_price` 是 0,卖回给 NPC 拿不到钱。
| 物品 | 价格(金币) |
|---|---|
@@ -38,6 +42,7 @@
| 双倍伤害丸、无视防御丸 | 各 15 万 |
| 经验药水、夺宝手套 | 各 20 万 |
| 物品属性转换秘籍 | 50 万 |
| 移动戒指(不限次数、无冷却,见 `../ring-warp`) | 1 |
| 追加物品属性秘籍、龙神的祝福书 | 各 100 万 |
## 坐骑(mount)
+50 -9
View File
@@ -16,10 +16,14 @@ MAPS="$SERVER/share/locale/$LOCALE/map"
export LC_ALL=C
# SHOP_VNUM, SHOP_NAME, NPC_VNUM, VILLAGES (one "map x y facing" line per village) and optionally NPC_NAME
# (UTF-8), the name shown over the NPC.
# (UTF-8), the name shown over the NPC; TAB_VNUMS, the shop vnums of pages 2 and 3 when items.tsv has
# [page name] lines.
NPC_NAME=
TAB_VNUMS=
. "$SHOP_DIR/shop.conf"
MOB_NAMES="$SERVER/share/conf/mob_names.txt"
SHOP_EX="$SERVER/share/locale/$LOCALE/shop_table_ex.txt"
EX_MARK="# npc-shops/install.sh $SHOP_NAME"
BACKUP=/root/backup-npcshop/$SHOP_NAME-$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP"
@@ -27,6 +31,7 @@ cp -p "$PROTO" "$BACKUP/item_proto.txt"
cp -p "$MOB_PROTO" "$BACKUP/mob_proto.txt"
cp -p "$MOB_NAMES" "$BACKUP/mob_names.txt"
echo "$VILLAGES" | while read -r map x y dir; do cp -p "$MAPS/$map/npc.txt" "$BACKUP/$map.npc.txt"; done
[ ! -f "$SHOP_EX" ] || cp -p "$SHOP_EX" "$BACKUP/shop_table_ex.txt"
mysqldump -uroot player shop shop_item item_proto mob_proto > "$BACKUP/player-shop.sql"
echo "backup: $BACKUP"
@@ -53,13 +58,49 @@ if [ -n "$NPC_NAME" ]; then
cat "$BACKUP/mob_names.new" > "$MOB_NAMES"
fi
{
awk -F'\t' '$1 !~ /^#/ && NF >= 3 { printf "UPDATE item_proto SET gold = %d WHERE vnum = %d;\n", $3, $1 }' "$ITEMS"
echo "UPDATE mob_proto SET on_click = 1 WHERE vnum = $NPC_VNUM;"
echo "REPLACE INTO shop (vnum, name, npc_vnum) VALUES ($SHOP_VNUM, '$SHOP_NAME', $NPC_VNUM);"
echo "DELETE FROM shop_item WHERE shop_vnum = $SHOP_VNUM;"
awk -F'\t' -v shop=$SHOP_VNUM '$1 !~ /^#/ && NF >= 3 { printf "INSERT INTO shop_item (shop_vnum, item_vnum, count) VALUES (%d, %d, %d);\n", shop, $1, $2 }' "$ITEMS"
} | mysql -uroot player
# A shop with pages ([page name] lines) is an extended shop: game reads it from shop_table_ex.txt, one
# group per page, and refuses an NPC that also has a shop in the DB.
if grep -q '^\[' "$ITEMS"; then
if [ -f "$SHOP_EX" ] && ! head -1 "$SHOP_EX" | grep -qxF "$EX_MARK"; then
echo "$SHOP_EX belongs to another shop" >&2; exit 1
fi
awk -F'\t' -v npc=$NPC_VNUM -v shop="$SHOP_NAME" -v vnums="$SHOP_VNUM $TAB_VNUMS" -v mark="$EX_MARK" '
/^\[/ { name[++pages] = substr($1, 2, length($1) - 2); next }
$1 !~ /^#/ && NF >= 3 { if (!pages) exit 1; n = ++count[pages]; row[pages, n] = $1 "\t" $2 "\t" $3 }
END {
split(vnums, vnum, " ")
if (pages > 3 || !(pages in vnum)) { print "items.tsv: " pages " pages, TAB_VNUMS too short or more than 3" > "/dev/stderr"; exit 1 }
print mark
print "Group\tShopNPC\n{\n\t#--#\tnpc\tgroup"
for (p = 1; p <= pages; p++) printf "\t%d\t%d\t%s_%d\n", p, npc, shop, p
print "}"
for (p = 1; p <= pages; p++) {
if (count[p] > 39) { print "items.tsv: page " p " has more than 39 items" > "/dev/stderr"; exit 1 }
# Rows are read in key order (a std::map), so the keys are zero-padded.
printf "Group\t%s_%d\n{\n\tVnum\t%d\n\tName\t%s\n\tGroup\titems\n\t{\n\t\t#--#\tvnum\tcount\tprice\n", shop, p, vnum[p], name[p]
for (i = 1; i <= count[p]; i++) printf "\t\t%02d\t%s\n", i, row[p, i]
print "\t}\n}"
}
}' "$ITEMS" > "$BACKUP/shop_table_ex.utf8"
# Page names go to the client in the locale's code page.
iconv -f UTF-8 -t GB2312 "$BACKUP/shop_table_ex.utf8" > "$SHOP_EX"
vnums=$(echo $SHOP_VNUM $TAB_VNUMS | tr ' ' ',')
{
awk -F'\t' '$1 !~ /^#/ && NF >= 3 { printf "UPDATE item_proto SET gold = %d WHERE vnum = %d;\n", $3, $1 }' "$ITEMS"
echo "UPDATE mob_proto SET on_click = 1 WHERE vnum = $NPC_VNUM;"
echo "DELETE FROM shop_item WHERE shop_vnum IN ($vnums);"
echo "DELETE FROM shop WHERE vnum IN ($vnums);"
} | mysql -uroot player
echo "$SHOP_EX: $(grep -c '^Group' "$SHOP_EX") groups"
else
{
awk -F'\t' '$1 !~ /^#/ && NF >= 3 { printf "UPDATE item_proto SET gold = %d WHERE vnum = %d;\n", $3, $1 }' "$ITEMS"
echo "UPDATE mob_proto SET on_click = 1 WHERE vnum = $NPC_VNUM;"
echo "REPLACE INTO shop (vnum, name, npc_vnum) VALUES ($SHOP_VNUM, '$SHOP_NAME', $NPC_VNUM);"
echo "DELETE FROM shop_item WHERE shop_vnum = $SHOP_VNUM;"
awk -F'\t' -v shop=$SHOP_VNUM '$1 !~ /^#/ && NF >= 3 { printf "INSERT INTO shop_item (shop_vnum, item_vnum, count) VALUES (%d, %d, %d);\n", shop, $1, $2 }' "$ITEMS"
} | mysql -uroot player
fi
echo "$VILLAGES" | while read -r map x y dir; do
npc="$MAPS/$map/npc.txt"
@@ -72,5 +113,5 @@ echo "$VILLAGES" | while read -r map x y dir; do
fi
done
mysql -uroot player -N -e "SELECT COUNT(*) FROM shop_item WHERE shop_vnum = $SHOP_VNUM"
grep -q '^\[' "$ITEMS" || mysql -uroot player -N -e "SELECT COUNT(*) FROM shop_item WHERE shop_vnum = $SHOP_VNUM"
echo "done; restart db and game (README.md)"
+4 -1
View File
@@ -1,4 +1,5 @@
# vnum count price name (duration: unique items count worn play minutes, 仓库扩张卷 real time)
# [tab name] starts a shop page (shop_table_ex.txt, at most 3 pages of 39); then vnum count price name (duration: unique items count worn play minutes, 仓库扩张卷 real time)
[增益]
72723 1 1 红龙的祝福(小)
72724 1 1 红龙的祝福(中)
72725 1 1 红龙的祝福(大)
@@ -28,6 +29,7 @@
72019 1 10000 仓库扩张卷 15分钟
72020 1 20000 仓库扩张卷 30分钟
72021 1 100000 仓库扩张卷 3小时
[道具]
72031 1 100000 龙神的攻击
72034 1 100000 龙神的防御
72037 1 100000 龙神的生命
@@ -39,3 +41,4 @@
71084 1 500000 物品属性转换秘籍
71085 1 1000000 追加物品属性秘籍
71032 1 1000000 龙神的祝福书
70058 1 1 移动戒指 不限次数、无冷却(../../ring-warp)
1 # vnum # [tab name] starts a shop page (shop_table_ex.txt, at most 3 pages of 39); then vnum count price name (duration: unique items count worn play minutes, 仓库扩张卷 real time)
2 [增益]
3 72723 72723 1 1 红龙的祝福(小)
4 72724 72724 1 1 红龙的祝福(中)
5 72725 72725 1 1 红龙的祝福(大)
29 72019 72019 1 10000 仓库扩张卷 15分钟
30 72020 72020 1 20000 仓库扩张卷 30分钟
31 72021 72021 1 100000 仓库扩张卷 3小时
32 [道具]
33 72031 72031 1 100000 龙神的攻击
34 72034 72034 1 100000 龙神的防御
35 72037 72037 1 100000 龙神的生命
41 71084 71084 1 500000 物品属性转换秘籍
42 71085 71085 1 1000000 追加物品属性秘籍
43 71032 71032 1 1000000 龙神的祝福书
44 70058 1 1 移动戒指 不限次数、无冷却(../../ring-warp)
@@ -5,3 +5,5 @@ NPC_VNUM=9010
VILLAGES="metin2_map_a1 686 565 8
metin2_map_b1 598 689 4
metin2_map_c1 391 693 5"
# Shop vnum of each page after the first (items.tsv has [tab] lines, so this is a shop_table_ex.txt shop).
TAB_VNUMS="16 17"