npc-shops: generic NPC shop installer + mount shop 15 on NPC 20368
tools/server/item-mall-shop becomes tools/server/npc-shops with one directory per shop (shop.conf + items.tsv) and install.sh <shop dir>. New mount shop: 35 ride tickets on NPC 20368 (renamed 坐骑商人 via NPC_NAME -> mob_names.txt) beside the stable boy in a1/b1/c1. Verified on 203: 35 slots at the drafted prices, bought + used 71114 -> mounted. zh overlay: 71118 虎坐骑券(黄), 20368 坐骑商人. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4bfc4244bd
commit
54fa2c0fc3
@@ -0,0 +1,89 @@
|
||||
# NPC 商店(203 服务端)
|
||||
|
||||
在三个村(a1 / b1 / c1)放按金币卖东西的 NPC 商店。每个商店一个目录:
|
||||
|
||||
- `shop.conf`:商店 vnum、名称、NPC vnum、每个村的 NPC 位置(`VILLAGES`,一行一个「地图 x y 朝向」),
|
||||
可选的 `NPC_NAME`(NPC 头顶显示的名字,UTF-8)。
|
||||
- `items.tsv`:vnum、数量、价格、名称,一行一件。最多 40 件(`SHOP_HOST_ITEM_MAX_NUM`,5×8 格)。
|
||||
客户端按 vnum 从小到大排列。
|
||||
|
||||
| 目录 | 商店 | NPC | 位置 |
|
||||
|---|---|---|---|
|
||||
| `item-mall/` | 14 `item_mall` | 9010 百店老板娘 | 杂货店 9003 旁 |
|
||||
| `mount/` | 15 `mount` | 20368 坐骑商人(原「铁算盘」,未使用的货郎模型) | 马夫 20349 旁 |
|
||||
|
||||
`install.sh` 对一个商店做这些事:
|
||||
|
||||
- 在 item_proto 的 `gold` 列写售价(`item_proto.txt` 第 10 列 / DB `item_proto.gold`)。
|
||||
- 把 NPC 在 mob_proto 里的 `on_click` 设为 1(ON_CLICK_SHOP,见 game `trigger.cpp`)。只有这个值会打开商店;
|
||||
9010 原来是 2,点击后没有反应。
|
||||
- 写 `shop` / `shop_item` 表。
|
||||
- 如果给了 `NPC_NAME`,就把名字写进 `mob_names.txt`(转成 GB2312)。
|
||||
- 如果某个村还没有这个 NPC,就在该村的 `npc.txt` 里加上。
|
||||
|
||||
## 商城(item-mall)
|
||||
|
||||
系统菜单里的「商城」按钮发 `/in_game_mall`,服务端回 `mall <url>`。203 没有网页商城,所以把商城物品放进 9010 的商店。
|
||||
客户端的 PORT 模块 `mt_mallhint` 会把这个按钮改成提示玩家去找百店老板娘。`item_mall.quest` 只在测试服下给 9010
|
||||
加对话选项,正式服不受影响。这 40 件物品的 `shop_buy_price` 是 0,卖回给 NPC 拿不到钱。
|
||||
|
||||
| 物品 | 价格(金币) |
|
||||
|---|---|
|
||||
| 红龙 / 蓝龙的祝福(全部 8 种) | 1 |
|
||||
| 经验戒指、小偷的手套、幸运金币 30 分 / 3 小时 / 10 小时 | 5 万 / 25 万 / 70 万 |
|
||||
| 丝绸包囊 30 分 / 3 小时 / 10 小时 | 3 万 / 15 万 / 40 万 |
|
||||
| 第三只手、兵法六统 15 分 / 30 分 / 3 小时 | 2 万 / 4 万 / 20 万 |
|
||||
| 仓库扩张卷 15 分 / 30 分 / 3 小时 | 1 万 / 2 万 / 10 万 |
|
||||
| 龙神的攻击 / 防御 / 生命 / 智力 | 各 10 万 |
|
||||
| 双倍伤害丸、无视防御丸 | 各 15 万 |
|
||||
| 经验药水、夺宝手套 | 各 20 万 |
|
||||
| 物品属性转换秘籍 | 50 万 |
|
||||
| 追加物品属性秘籍、龙神的祝福书 | 各 100 万 |
|
||||
|
||||
## 坐骑(mount)
|
||||
|
||||
坐骑券都由 ride 系列 quest 处理(`pc.mount`)。骑乘时长 = 物品 socket 2 × 60 秒,道具创建时由 value0 填入。
|
||||
|
||||
| 物品 | 骑乘时长 | 价格(金币) |
|
||||
|---|---|---|
|
||||
| 野猪 / 狼 / 虎 / 狮坐骑券(黄),75–85 级,用后消失 | 5 小时 | 5 万 / 8 万 / 10 万 / 15 万 |
|
||||
| 野猪 / 狼 / 虎 / 狮坐骑券(蓝),75–85 级,可反复使用 | 每次 24 小时 | 200 万 / 300 万 / 400 万 / 500 万 |
|
||||
| 万圣黑马、彗星驯鹿、战甲白熊、战甲熊猫、白独角兽、梦魇 | 3 小时 | 各 3 万 |
|
||||
| 白狮坐骑券 | 40 小时 | 30 万 |
|
||||
| 黑马召唤券 | 180 / 360 / 720 / 1440 小时 | 50 万 / 90 万 / 160 万 / 300 万 |
|
||||
| 战野猪 / 战狼 / 风暴虎 / 战狮坐骑券(20 级) | 480 小时 | 各 150 万 |
|
||||
| 皇虎召唤券(蓝 / 红 / 金 / 绿 / 灰 / 白) | 480 小时 | 各 150 万 |
|
||||
| 勇猛狼 / 狮 / 虎 / 驯鹿(公、母)/ 石龙子召唤牌,攻击 +100 | 1 年;召唤牌本身 7 天后消失 | 各 500 万 |
|
||||
|
||||
定价思路:短时券便宜,方便试骑;按小时算,长时券更划算;蓝券可以反复使用,所以定价约为黄券的 40 倍;
|
||||
带属性加成的勇猛召唤牌最贵。
|
||||
|
||||
## 安装
|
||||
|
||||
```sh
|
||||
scp -r tools/server/npc-shops root@<server>:/root/
|
||||
ssh root@<server> 'cd /root/npc-shops && sh install.sh item-mall && sh install.sh mount'
|
||||
ssh root@<server> 'cd /usr/metin2/server && sh close.sh; sleep 8; echo 1 | sh start.sh'
|
||||
```
|
||||
|
||||
db 只在启动时读取 item_proto、mob_proto 和商店表,game 只在启动时读取 npc.txt 和 mob_names.txt,所以装完必须重启。
|
||||
脚本可以重复运行:价格、名字和商店每次都按目录内容重写;已经放了这个 NPC 的村不会再加一个。
|
||||
改价格就编辑 `items.tsv`,然后重新安装。
|
||||
|
||||
## 回滚
|
||||
|
||||
每次运行都会备份到 `/root/backup-npcshop/<商店名>-<时间>/`:
|
||||
|
||||
```sh
|
||||
B=/root/backup-npcshop/<商店名>-<时间>
|
||||
cp $B/item_proto.txt $B/mob_proto.txt $B/mob_names.txt /usr/metin2/server/share/conf/
|
||||
for m in metin2_map_a1 metin2_map_b1 metin2_map_c1; do
|
||||
cp $B/$m.npc.txt /usr/metin2/server/share/locale/newcibn/map/$m/npc.txt
|
||||
done
|
||||
mysql -uroot player < $B/player-shop.sql
|
||||
```
|
||||
|
||||
回滚后同样要重启。第一次改动之前的状态:
|
||||
|
||||
- 商城:`/root/backup-itemmall/20260929-162714`。这份还没备份 mob_proto;9010 原来的 on_click 是 2。
|
||||
- 坐骑:`/root/backup-npcshop/mount-20260929-172630`。更早的 mount 备份里还没有 mob_names.txt;20368 原名是 Peddler。
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/bin/sh
|
||||
# Installs one NPC shop (README.md): sh install.sh <shop dir>, where the directory has shop.conf (the shop,
|
||||
# its NPC and where the NPC stands) and items.tsv (what it sells, at which price). Run on the server, then
|
||||
# restart db + game. Safe to run again: prices and the shop are rewritten from items.tsv, the NPC is only
|
||||
# added to a village that does not have one yet.
|
||||
set -eu
|
||||
|
||||
[ $# -eq 1 ] || { echo "usage: sh install.sh <shop dir>" >&2; exit 2; }
|
||||
SHOP_DIR=$(cd "$1" && pwd)
|
||||
SERVER=${SERVER:-/usr/metin2/server}
|
||||
LOCALE=${LOCALE:-newcibn}
|
||||
ITEMS="$SHOP_DIR/items.tsv"
|
||||
PROTO="$SERVER/share/conf/item_proto.txt"
|
||||
MOB_PROTO="$SERVER/share/conf/mob_proto.txt"
|
||||
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.
|
||||
NPC_NAME=
|
||||
. "$SHOP_DIR/shop.conf"
|
||||
MOB_NAMES="$SERVER/share/conf/mob_names.txt"
|
||||
BACKUP=/root/backup-npcshop/$SHOP_NAME-$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
mkdir -p "$BACKUP"
|
||||
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
|
||||
mysqldump -uroot player shop shop_item item_proto mob_proto > "$BACKUP/player-shop.sql"
|
||||
echo "backup: $BACKUP"
|
||||
|
||||
# item_proto.txt column 10 is the price an NPC shop sells the item for (TItemTable::dwGold).
|
||||
awk -F'\t' -v OFS='\t' 'NR == FNR { if ($1 !~ /^#/ && NF >= 3) { price[$1] = $3; want++ } next }
|
||||
($1 in price) { $10 = price[$1]; n++ } { print }
|
||||
END { if (n != want) { print "item_proto.txt: " n " of " want " items found" > "/dev/stderr"; exit 1 } }' \
|
||||
"$ITEMS" "$PROTO" > "$BACKUP/item_proto.new"
|
||||
cat "$BACKUP/item_proto.new" > "$PROTO"
|
||||
|
||||
# mob_proto.txt column 14 is ON_CLICK: the NPC only opens its shop with 1 (ON_CLICK_SHOP, trigger.cpp);
|
||||
# 9010 ships with 2, which has no handler.
|
||||
awk -F'\t' -v OFS='\t' -v v=$NPC_VNUM '$1 == v { $14 = 1; n++ } { print }
|
||||
END { if (n != 1) { print "mob_proto.txt: NPC " v " not found" > "/dev/stderr"; exit 1 } }' \
|
||||
"$MOB_PROTO" > "$BACKUP/mob_proto.new"
|
||||
cat "$BACKUP/mob_proto.new" > "$MOB_PROTO"
|
||||
|
||||
# mob_names.txt is in the locale's code page (GB2312 for newcibn).
|
||||
if [ -n "$NPC_NAME" ]; then
|
||||
name=$(printf '%s' "$NPC_NAME" | iconv -f UTF-8 -t GB2312)
|
||||
awk -F'\t' -v OFS='\t' -v v=$NPC_VNUM -v name="$name" '$1 == v { $2 = name; n++ } { print }
|
||||
END { if (n != 1) { print "mob_names.txt: NPC " v " not found" > "/dev/stderr"; exit 1 } }' \
|
||||
"$MOB_NAMES" > "$BACKUP/mob_names.new"
|
||||
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
|
||||
|
||||
echo "$VILLAGES" | while read -r map x y dir; do
|
||||
npc="$MAPS/$map/npc.txt"
|
||||
if awk -F'\t' -v v=$NPC_VNUM '$11 == v { found = 1 } END { exit !found }' "$npc"; then
|
||||
echo "$map: NPC $NPC_VNUM already placed"
|
||||
else
|
||||
[ -z "$(tail -c 1 "$npc")" ] || echo >> "$npc"
|
||||
printf 'm\t%d\t%d\t0\t0\t0\t%d\t1m\t100\t1\t%d\n' "$x" "$y" "$dir" "$NPC_VNUM" >> "$npc"
|
||||
echo "$map: NPC $NPC_VNUM at $x,$y"
|
||||
fi
|
||||
done
|
||||
|
||||
mysql -uroot player -N -e "SELECT COUNT(*) FROM shop_item WHERE shop_vnum = $SHOP_VNUM"
|
||||
echo "done; restart db and game (README.md)"
|
||||
@@ -0,0 +1,41 @@
|
||||
# vnum count price name (duration: unique items count worn play minutes, 仓库扩张卷 real time)
|
||||
72723 1 1 红龙的祝福(小)
|
||||
72724 1 1 红龙的祝福(中)
|
||||
72725 1 1 红龙的祝福(大)
|
||||
72726 1 1 红龙的祝福(特)
|
||||
72727 1 1 蓝龙的祝福(小)
|
||||
72728 1 1 蓝龙的祝福(中)
|
||||
72729 1 1 蓝龙的祝福(大)
|
||||
72730 1 1 蓝龙的祝福(特)
|
||||
72002 1 50000 经验戒指 30分钟
|
||||
72003 1 250000 经验戒指 3小时
|
||||
72001 1 700000 经验戒指 10小时
|
||||
72005 1 50000 小偷的手套 30分钟
|
||||
72006 1 250000 小偷的手套 3小时
|
||||
72004 1 700000 小偷的手套 10小时
|
||||
72023 1 50000 幸运金币 30分钟
|
||||
72024 1 250000 幸运金币 3小时
|
||||
72022 1 700000 幸运金币 10小时
|
||||
72008 1 30000 丝绸包囊 30分钟
|
||||
72009 1 150000 丝绸包囊 3小时
|
||||
72007 1 400000 丝绸包囊 10小时
|
||||
72016 1 20000 第三只手 15分钟
|
||||
72017 1 40000 第三只手 30分钟
|
||||
72018 1 200000 第三只手 3小时
|
||||
72043 1 20000 兵法六统 15分钟
|
||||
72044 1 40000 兵法六统 30分钟
|
||||
72045 1 200000 兵法六统 3小时
|
||||
72019 1 10000 仓库扩张卷 15分钟
|
||||
72020 1 20000 仓库扩张卷 30分钟
|
||||
72021 1 100000 仓库扩张卷 3小时
|
||||
72031 1 100000 龙神的攻击
|
||||
72034 1 100000 龙神的防御
|
||||
72037 1 100000 龙神的生命
|
||||
72040 1 100000 龙神的智力
|
||||
72046 1 150000 双倍伤害丸
|
||||
72025 1 150000 无视防御丸
|
||||
71153 1 200000 经验药水
|
||||
70043 1 200000 夺宝手套 2小时
|
||||
71084 1 500000 物品属性转换秘籍
|
||||
71085 1 1000000 追加物品属性秘籍
|
||||
71032 1 1000000 龙神的祝福书
|
||||
|
@@ -0,0 +1,7 @@
|
||||
# The item mall: NPC 9010 百店老板娘 next to the general store 9003 in each village.
|
||||
SHOP_VNUM=14
|
||||
SHOP_NAME=item_mall
|
||||
NPC_VNUM=9010
|
||||
VILLAGES="metin2_map_a1 686 565 8
|
||||
metin2_map_b1 598 689 4
|
||||
metin2_map_c1 391 693 5"
|
||||
@@ -0,0 +1,36 @@
|
||||
# vnum count price name (mount time; unique items count worn play minutes, quest ride.quest / ride_*.quest)
|
||||
71114 1 50000 野猪坐骑券(黄) 5小时 用后消失 75级
|
||||
71116 1 80000 狼坐骑券(黄) 5小时 用后消失 80级
|
||||
71118 1 100000 虎坐骑券(黄) 5小时 用后消失 85级
|
||||
71120 1 150000 狮坐骑券(黄) 5小时 用后消失 85级
|
||||
71115 1 2000000 野猪坐骑券(蓝) 24小时 可反复使用 75级
|
||||
71117 1 3000000 狼坐骑券(蓝) 24小时 可反复使用 80级
|
||||
71119 1 4000000 虎坐骑券(蓝) 24小时 可反复使用 85级
|
||||
71121 1 5000000 狮坐骑券(蓝) 24小时 可反复使用 85级
|
||||
71161 1 30000 万圣黑马召唤券 3小时
|
||||
71164 1 30000 彗星驯鹿召唤券 3小时
|
||||
71165 1 30000 战甲白熊召唤牌 3小时
|
||||
71166 1 30000 战甲熊猫召唤牌 3小时
|
||||
71171 1 30000 白独角兽召唤牌 3小时
|
||||
71172 1 30000 梦魇召唤牌 3小时
|
||||
71124 1 300000 白狮坐骑券 40小时
|
||||
71131 1 500000 黑马召唤券 180小时
|
||||
71132 1 900000 黑马召唤券 360小时
|
||||
71133 1 1600000 黑马召唤券 720小时
|
||||
71134 1 3000000 黑马召唤券 1440小时
|
||||
71125 1 1500000 战野猪坐骑券 480小时 20级
|
||||
71126 1 1500000 战狼坐骑券 480小时 20级
|
||||
71127 1 1500000 风暴虎坐骑券 480小时 20级
|
||||
71128 1 1500000 战狮坐骑券 480小时 20级
|
||||
71137 1 1500000 皇虎召唤券(蓝) 480小时
|
||||
71138 1 1500000 皇虎召唤券(红) 480小时
|
||||
71139 1 1500000 皇虎召唤券(金) 480小时
|
||||
71140 1 1500000 皇虎召唤券(绿) 480小时
|
||||
71141 1 1500000 皇虎召唤券(灰) 480小时
|
||||
71142 1 1500000 皇虎召唤券(白) 480小时
|
||||
52030 1 5000000 勇猛狼召唤牌 1年 攻击+100
|
||||
52045 1 5000000 勇猛狮召唤牌 1年 攻击+100
|
||||
52060 1 5000000 勇猛虎召唤牌 1年 攻击+100
|
||||
52075 1 5000000 勇猛驯鹿召唤牌(公) 1年 攻击+100
|
||||
52090 1 5000000 勇猛驯鹿召唤牌(母) 1年 攻击+100
|
||||
52105 1 5000000 勇猛石龙子召唤牌 1年 攻击+100
|
||||
|
@@ -0,0 +1,9 @@
|
||||
# The mount shop: NPC 20368 (peddler model, unused, no quest, ON_CLICK already 1), renamed 坐骑商人, next to
|
||||
# the stable boy 20349 in each village.
|
||||
SHOP_VNUM=15
|
||||
SHOP_NAME=mount
|
||||
NPC_VNUM=20368
|
||||
NPC_NAME=坐骑商人
|
||||
VILLAGES="metin2_map_a1 724 577 1
|
||||
metin2_map_b1 555 610 2
|
||||
metin2_map_c1 402 735 2"
|
||||
Reference in New Issue
Block a user