Skip to content

Commit 82a2f59

Browse files
committed
Fix 4 bugs found in xiakexinzhuan2's deep functional test, incl. 2 new
§7.31/§7.32 classes Round-two deep functional test (AGENTS.md §10.7), one continuous session plus deliberate unclean-disconnect and driver-restart cycles. 1. AGENTS.md §8.3a addendum match: feature/action.lpc's eval_function() was private, the shared choke point for 59 kungfu-skill/drug files' delayed-effect call_outs, silently no-oping all of them. Dropped private. 2. New bug class (§7.31): adm/daemons/logind.lpc's enter_world() unconditionally overwrote the just-restored persistent player object's "registered" flag with the fresh per-connection object's stale/default value -- silently un-registering every returning player on every single full login and rerouting them to the newbie register room instead of their real startroom. Fixed by treating the flag as monotonic (true on either object wins). 3. New bug class (§7.32): d/{city,rooms}/npc/xu.lpc's do_go() (the paid travel-guide NPC, the only way to reach the lib's sole open sect since there's no walkable road) checked each destination with independent ifs (no else-chaining) ending in a single trailing else -- which, by C-family binding rules, only covered the LAST destination checked. Every other valid destination got its flag set and then was immediately rejected with a generic failure message. Fixed by converting to a proper if/else-if/else chain. Live- verified: reached the sect via the fixed NPC. 4. AGENTS.md §7.12 class (existing, cited not redrafted): adm/simul_efun/message.lpc's message_system() and tell_room() both passed a raw int 0 as message()'s exclude/4th argument, crashing every broadcast and (via tell_room()) the net-dead force-quit handler. Fixed both. Also independently hit a third occurrence of AGENTS.md §10.8's driver-fatal crash class during the net-dead wait -- not fixed (driver-level, already documented). Left the training dummy's 12000-combat_exp accept_fight() gate untouched as genuinely ambiguous content/design, per the task's scope filter.
1 parent 3dc9037 commit 82a2f59

9 files changed

Lines changed: 658 additions & 28 deletions

File tree

AGENTS.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,6 +1939,73 @@ variable. At an inline `query(...)["key"]` chain, capture the query
19391939
result into a local first and guard with `mapp()` before indexing —
19401940
don't index the `query()` call's return value directly.
19411941

1942+
### 7.31 `enter_world()` overwrites the just-restored persistent player object's flag with the fresh per-connection object's stale/default value
1943+
1944+
Found on `xiakexinzhuan2`'s deep functional test (§10.7). On login, two
1945+
distinct objects exist briefly: the persistent player body (`user`,
1946+
restored from the player's save file, carrying their real accumulated
1947+
state) and a brand-new per-connection login/network object (`ob`,
1948+
created fresh for this one connection attempt, whose own properties are
1949+
either never set or explicitly zeroed during the new-character-creation
1950+
path). `enter_world()` did `user->set("registered",
1951+
ob->query("registered"))` — copying `ob`'s always-stale-or-zero value
1952+
onto `user`, unconditionally overwriting whatever the player's own save
1953+
data had correctly restored. Net effect: a boolean flag that's supposed
1954+
to persist forever once set true (`registered`, set permanently by a
1955+
one-time registration NPC interaction, `this_player()->set("registered",
1956+
1)`) got silently reset to false on EVERY subsequent full login,
1957+
re-triggering the entire registration flow and rerouting an established
1958+
player back to the newbie register room instead of their real
1959+
`startroom`.
1960+
1961+
Detection: any `enter_world()`/login-flow code doing
1962+
`user->set(<flag>, ob->query(<flag>))` (or the reverse) — for a
1963+
supposedly-persistent flag, this is backwards unless `ob` is genuinely
1964+
the current source of truth for that specific property. Check where the
1965+
flag is actually SET elsewhere in the lib (grep for
1966+
`set("<flag_name>"` broadly) — if it's set on the persistent player body
1967+
by unrelated gameplay code (an NPC interaction, a quest completion), not
1968+
freshly derived from the connection object every login, blindly copying
1969+
from `ob` will stomp it.
1970+
1971+
Fix: treat the flag as monotonic where that's the correct semantics —
1972+
true on either object wins (`if (ob->query(f) || user->query(f)) {
1973+
ob->set(f, 1); user->set(f, 1); } else user->set(f, 0);`) — or, more
1974+
simply, just stop writing to `user` from `ob` for any property that's
1975+
supposed to already be correctly restored on `user` from its own save
1976+
data.
1977+
1978+
### 7.32 A dangling/missing `else` in a sequential `if`-chain silently rejects every case but the last
1979+
1980+
Found on `xiakexinzhuan2`'s deep functional test (§10.7). A classic
1981+
control-flow defect, not lib-architecture-specific, but worth cataloging
1982+
since it produced a severe, silent, near-total feature failure: a
1983+
multi-destination dispatcher (a paid travel-guide NPC's `do_go()`)
1984+
checked each valid destination with an INDEPENDENT `if (target ==
1985+
"X") me->set_temp("go_x", 1);` — no `else` chaining between them — and
1986+
ended with a single `if (target == "<last option>") ...; else return
1987+
notify_fail(...)`. Because C-family `else` binds only to its immediately
1988+
preceding `if`, that trailing `else` fires for EVERY target that isn't
1989+
the LAST option checked — even after an EARLIER `if` in the same chain
1990+
already matched and correctly set that destination's flag. Result: only
1991+
the last-checked destination ever actually worked; every other valid,
1992+
correctly-recognized destination string got its flag set and then was
1993+
immediately rejected with a generic "never been there" failure message,
1994+
because the function still fell through to the final unconditional
1995+
`if/else` before ever reaching the success path (`call_out("do_goto",
1996+
0, me); return 1;`) at the bottom.
1997+
1998+
Detection: read the FULL body of any multi-branch dispatcher built from
1999+
sequential `if`s ending in a single trailing `else` — don't assume the
2000+
`else` covers "none of the above" for the whole chain just because
2001+
that's the common intent; check whether it's actually only attached to
2002+
the last `if`. Reproduce live by trying every documented option, not
2003+
just the first or last one a smoke test happens to pick.
2004+
2005+
Fix: convert the sequential `if`s into a proper `if`/`else if`/…/`else`
2006+
chain so the trailing `else` genuinely covers "none of the preceding
2007+
conditions matched," not just "didn't match the last one."
2008+
19422009
---
19432010

19442011
## 8. Login and registration flow bugs

libs/xiakexinzhuan2/NOTES.md

Lines changed: 535 additions & 0 deletions
Large diffs are not rendered by default.

libs/xiakexinzhuan2/work/adm/daemons/logind.lpc

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,24 @@ varargs void enter_world(object ob, object user, int silent) {
510510
user->set_temp("link_ob", ob);
511511
ob->set_temp("body_ob", user);
512512
user->set_temp("big5", ob->query_temp("big5"));
513-
user->set("registered", ob->query("registered"));
513+
// "registered" is set permanently on `user` by
514+
// /d/register/npc/shuisheng.lpc's do_decide() (this_player() there is
515+
// always `user`, never the per-connection `ob`), but `ob` is a brand
516+
// new /clone/user/user/login object every connection whose own
517+
// "registered" field is only ever initialized to 0 (see the new-
518+
// character-creation path above). Blindly overwriting `user`'s
519+
// already-restored, correctly-persisted "registered" with `ob`'s
520+
// permanently-stale 0 silently un-registers every returning player on
521+
// every single fresh login (not net-dead reconnect, which doesn't call
522+
// this function), re-triggering the "还没有注册" flow and rerouting
523+
// them to REGISTER_ROOM instead of their real startroom. Treat it as a
524+
// monotonic flag: true on either object wins, and keep both in sync.
525+
if (ob->query("registered") || user->query("registered")) {
526+
ob->set("registered", 1);
527+
user->set("registered", 1);
528+
} else {
529+
user->set("registered", 0);
530+
}
514531

515532
if (interactive(ob)) exec(user, ob);
516533

libs/xiakexinzhuan2/work/adm/simul_efun/message.lpc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ void tell_object(object ob, string str) {
262262
}
263263

264264
varargs void tell_room(mixed ob, string str, object *exclude) {
265-
if (ob) message("tell_room", str, ob, exclude);
265+
if (ob) message("tell_room", str, ob, exclude || ({}));
266266
}
267267

268268
void shout(string str) {
@@ -362,7 +362,7 @@ string clean_color(string arg) {
362362
}
363363

364364
void message_system(string message) {
365-
message("system", HIW "\n【系统提示】" + message + "\n" NOR, users(), 0);
365+
message("system", HIW "\n【系统提示】" + message + "\n" NOR, users());
366366
}
367367

368368
int notify_fail(string msg) {

libs/xiakexinzhuan2/work/d/city/npc/xu.lpc

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,23 @@ int do_go(string target) {
7171
return notify_fail("徐霞客皱了皱眉:那地方我可没去过。\n");
7272

7373
if (target == "回疆") me->set_temp("go_hj", 1);
74-
if (target == "星宿" || target == "星宿海") me->set_temp("go_xx", 1);
75-
if (target == "伊犁" || target == "伊犁城") me->set_temp("go_yili", 1);
76-
if (target == "少林" || target == "少林寺" ||
74+
else if (target == "星宿" || target == "星宿海") me->set_temp("go_xx", 1);
75+
else if (target == "伊犁" || target == "伊犁城") me->set_temp("go_yili", 1);
76+
else if (target == "少林" || target == "少林寺" ||
7777
target == "嵩山少林寺" || target == "嵩山") me->set_temp("go_sl", 1);
78-
if (target == "武当" || target == "武当山") me->set_temp("go_wd", 1);
79-
if (target == "扬州" || target == "扬州城") me->set_temp("go_yz", 1);
80-
/*
81-
if (target=="大理" || target=="大理城") me->set_temp("go_dali", 1);
78+
else if (target == "武当" || target == "武当山") me->set_temp("go_wd", 1);
79+
else if (target == "扬州" || target == "扬州城") me->set_temp("go_yz", 1);
80+
/*
81+
if (target=="大理" || target=="大理城") me->set_temp("go_dali", 1);
8282
if (target=="昆仑" || target=="昆仑山") me->set_temp("go_kl", 1);
8383
if (target=="铁掌帮" || target=="铁掌峰" ||
84-
target=="猴爪山") me->set_temp("go_tzb", 1);
84+
target=="猴爪山") me->set_temp("go_tzb", 1);
8585
if (target=="桃花岛" || target=="东海渔港") me->set_temp("go_thd", 1);
8686
*/
87-
if (target == "明教" || target == "光明顶") me->set_temp("go_mj", 1);
88-
if (target == "苏州" || target == "苏州城") me->set_temp("go_sz", 1);
89-
if (target == "终南山" || target == "全真教") me->set_temp("go_zns", 1);
90-
if (target == "杭州" || target == "杭州城") me->set_temp("go_hz", 1);
87+
else if (target == "明教" || target == "光明顶") me->set_temp("go_mj", 1);
88+
else if (target == "苏州" || target == "苏州城") me->set_temp("go_sz", 1);
89+
else if (target == "终南山" || target == "全真教") me->set_temp("go_zns", 1);
90+
else if (target == "杭州" || target == "杭州城") me->set_temp("go_hz", 1);
9191
else return notify_fail("徐霞客皱了皱眉:那地方我可没去过。\n");
9292
call_out("do_goto", 0, me);
9393
return 1;

libs/xiakexinzhuan2/work/d/rooms/npc/xu.lpc

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,23 @@ int do_go(string target) {
7070
return notify_fail("徐霞客皱了皱眉:那地方我可没去过。\n");
7171

7272
if (target == "回疆") me->set_temp("go_hj", 1);
73-
if (target == "星宿" || target == "星宿海") me->set_temp("go_xx", 1);
74-
if (target == "伊犁" || target == "伊犁城") me->set_temp("go_yili", 1);
75-
if (target == "少林" || target == "少林寺" ||
73+
else if (target == "星宿" || target == "星宿海") me->set_temp("go_xx", 1);
74+
else if (target == "伊犁" || target == "伊犁城") me->set_temp("go_yili", 1);
75+
else if (target == "少林" || target == "少林寺" ||
7676
target == "嵩山少林寺" || target == "嵩山") me->set_temp("go_sl", 1);
77-
if (target == "武当" || target == "武当山") me->set_temp("go_wd", 1);
78-
if (target == "扬州" || target == "扬州城") me->set_temp("go_yz", 1);
79-
/*
80-
if (target=="大理" || target=="大理城") me->set_temp("go_dali", 1);
77+
else if (target == "武当" || target == "武当山") me->set_temp("go_wd", 1);
78+
else if (target == "扬州" || target == "扬州城") me->set_temp("go_yz", 1);
79+
/*
80+
if (target=="大理" || target=="大理城") me->set_temp("go_dali", 1);
8181
if (target=="昆仑" || target=="昆仑山") me->set_temp("go_kl", 1);
8282
if (target=="铁掌帮" || target=="铁掌峰" ||
83-
target=="猴爪山") me->set_temp("go_tzb", 1);
83+
target=="猴爪山") me->set_temp("go_tzb", 1);
8484
if (target=="桃花岛" || target=="东海渔港") me->set_temp("go_thd", 1);
8585
*/
86-
if (target == "明教" || target == "光明顶") me->set_temp("go_mj", 1);
87-
if (target == "苏州" || target == "苏州城") me->set_temp("go_sz", 1);
88-
if (target == "终南山" || target == "全真教") me->set_temp("go_zns", 1);
89-
if (target == "杭州" || target == "杭州城") me->set_temp("go_hz", 1);
86+
else if (target == "明教" || target == "光明顶") me->set_temp("go_mj", 1);
87+
else if (target == "苏州" || target == "苏州城") me->set_temp("go_sz", 1);
88+
else if (target == "终南山" || target == "全真教") me->set_temp("go_zns", 1);
89+
else if (target == "杭州" || target == "杭州城") me->set_temp("go_hz", 1);
9090
else return notify_fail("徐霞客皱了皱眉:那地方我可没去过。\n");
9191
call_out("do_goto", 0, me);
9292
return 1;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#/clone/user/login.lpc
2+
dbase (["registered":1,"password":"$6$munvVXsTqOazp2ay$27zQ1ihblVxU0EEt6Z7s9092RoS3jfYjSSpGZ20eW1uetFu1qLoD62cZgsWutycquVfiAx3xStbMjswWkf1/e/","last_from":"127.0.0.1 40035","id":"qingyunk","last_on":1784952795,"body":"/clone/user/user","name":"青云客",])
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#/clone/user/user.lpc
2+
killer ({})
3+
want_kills ({})
4+
dbase (["max_jing":100,"qi":100,"title":"武当派第四代弟子","special_skill":(["mystery":1,]),"dex":24,"jing":100,"water":279,"channels":({"chat","rumor","party","bill","sos","family","ic","rultra",}),"birthday":1784949818,"per":23,"race":"人类","sec_id":"$6$W.QnN28SwUCkrl7y$uqRAX/CCy9RaSV/lUpC9oE3.nonYUv4rL267hW7UIBN0eVOSwGQySu2XU02cWMzSyHttLfrmST8wfvhr8EA1A.","env":(["prompt":"time","wimpy":60,]),"startroom":"/d/city/zuixianlou","gender":"男性","character":"光明磊落","con":17,"eff_jing":100,"type":"均衡型","potential":99,"born":"扬州人氏","name":"青云客","mud_age":833,"int":21,"registered":1,"id":"qingyunk","attitude":"peaceful","enter_wuguan":1,"limbs":({"头部","颈部","胸口","后心","左肩","右肩","左臂","右臂","左手","右手","腰间","小腹","左腿","右腿","左脚","右脚",}),"last_read_news":1784949818,"food":279,"learned_points":1,"kar":12,"email":"qingyunk@example.com","last_save":1784952763,"shen_type":0,"max_qi":100,"unit":"位","age":14,"can_speak":1,"str":18,"family":(["enter_time":1784952097,"title":"弟子","privs":0,"master_id":"guxu daozhang","family_name":"武当派","master_name":"谷虚道长","generation":4,]),"born_family":"没有","shen":0,"eff_qi":100,])
5+
skills (["force":1,])
6+
learned (["force":0,])
7+
autoload ({"/clone/money/coin:70",})
8+
at_time 1784952794
9+
ban_say_msg ""

libs/xiakexinzhuan2/work/feature/action.lpc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ int start_call_out(function fun, int delay) {
108108
return 1;
109109
}
110110

111-
private void eval_function(function fun) { evaluate(fun); }
111+
void eval_function(function fun) { evaluate(fun); }
112112

113113
// I would let some function override the old function,
114114
// such as the player unconcious/die ...

0 commit comments

Comments
 (0)