Skip to content

Commit d607aac

Browse files
author
Alex Smith
committed
Ensure a consistent view of time in multiplayer games
Ignore-this: 6c5f44255ecd2d5e98c1bf7f421edf52 darcs-hash:20110704223446-99289-130ee76c45bb3673da4964794551a4cd57f7d8b7
1 parent 17fe5b0 commit d607aac

File tree

6 files changed

+194
-19
lines changed

6 files changed

+194
-19
lines changed

include/extern.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,7 @@ E boolean FDECL(obj_is_local, (struct obj *));
20672067
E void FDECL(save_timers, (int,int,int));
20682068
E void FDECL(restore_timers, (int,int,BOOLEAN_P,long));
20692069
E void FDECL(relink_timers, (BOOLEAN_P));
2070+
E void FDECL(adjust_nonlocal_timers, (long));
20702071
#ifdef WIZARD
20712072
E int NDECL(wiz_timeout_queue);
20722073
E void NDECL(timer_sanity_check);

src/allmain.c

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -823,16 +823,20 @@ found_game_to_attach_to: ;
823823
right down the pipe. */
824824
restore_dungeon(mpcfd);
825825

826-
/* A bit of a weird dependency issue here. We have to uncheckpoint
827-
before placing the player on the stairs, or we don't know where
828-
the stairs are. But we have to place the player on the stairs
829-
before uncheckpointing, or the game tries to move the cursor
830-
off the map, leading to an infinite loop. The solution is to
831-
get the coordinates from the other game. */
826+
/* A bit of a weird dependency issue here. We have to
827+
uncheckpoint before placing the player on the stairs, or we
828+
don't know where the stairs are. But we have to place the
829+
player on the stairs before uncheckpointing, or the game
830+
tries to move the cursor off the map, leading to an
831+
infinite loop. The solution is to get the coordinates from
832+
the other game. We also get the current time from the other
833+
game, because the timings must be approximately in sync. */
832834
if (read(mpcfd, &u.ux, sizeof(u.ux)) <= 0 ||
833-
read(mpcfd, &u.uy, sizeof(u.uy)) <= 0) {
835+
read(mpcfd, &u.uy, sizeof(u.uy)) <= 0 ||
836+
read(mpcfd, &monstermoves, sizeof(monstermoves)) <= 0) {
834837
panic("Multiplayer control pipe read failure");
835838
}
839+
moves = monstermoves;
836840

837841
/* Remove watchdog timer. */
838842
alarm(0);

src/apply.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* SCCS Id: @(#)apply.c 3.4 2003/11/18 */
22
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3-
/* Modified 19 Aug 2011 by Alex Smith */
3+
/* Modified 16 Jun 2011 by Alex Smith */
44
/* NetHack may be freely redistributed. See license for details. */
55

66
#include "hack.h"
@@ -422,6 +422,10 @@ struct obj *obj;
422422
register struct monst *mtmp;
423423
int spotmon;
424424

425+
if(iflags.multiplayer) {
426+
pline("Leashes do not work in multiplayer games.");
427+
return;
428+
}
425429
if(!obj->leashmon && number_leashed() >= MAXLEASHED) {
426430
You("cannot leash any more pets.");
427431
return;

src/cmd.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -567,16 +567,29 @@ doinvite()
567567
}
568568

569569
/* Avoid multiplayer-unsafe or yield-unsafe circumstances. */
570+
if (iflags.multiplayer && strcmp(iflags.mp_lock_name, mplock))
571+
{ /* would garble lockfiles, and is anyway best from a game management
572+
point of view */
573+
pline("All invitations into a game must be done by the same person.");
574+
return 0;
575+
}
570576
if (u.usteed)
571-
{
577+
{ /* multiplayer-unsafe (pointer from lockfile 0 to other lockfiles) */
572578
You_cant("ride a steed in multiplayer.");
573579
return 0;
574580
}
575581
if (u.ustuck)
576-
{
582+
{ /* yield-unsafe */
577583
You_cant("invite a player while %s has you trapped!", a_monnam(u.ustuck));
578584
return 0;
579585
}
586+
/* This limitation is not because it would crash the program, but because
587+
it effectively leashes the pet to all players on the level at once,
588+
which is not at all what people expect. */
589+
if (number_leashed()) {
590+
You_cant("leash pets in multiplayer. Unleash them before inviting.");
591+
return 0;
592+
}
580593

581594
/* This leaks a tiny bit of information. Oh well... */
582595
if ((mtmp = m_at(sstairs.sx,sstairs.sy))) {
@@ -634,10 +647,12 @@ doinvite()
634647
save_dungeon(fd, TRUE, FALSE);
635648
/* It needs to know the player's coordinates, too, to do things
636649
in the right order. x and y are the same size as u.ux and
637-
u.uy, or this wouldn't work. */
650+
u.uy, or this wouldn't work. Likewise, we need to ensure that
651+
the games have a consistent view of time. */
638652
x = sstairs.sx; y = sstairs.sy;
639653
if (write(fd, &x, sizeof(u.ux)) <= 0 ||
640-
write(fd, &y, sizeof(u.uy)) <= 0) {
654+
write(fd, &y, sizeof(u.uy)) <= 0 ||
655+
write(fd, &monstermoves, sizeof(monstermoves)) <= 0) {
641656
panic("Multiplayer control pipe write failure");
642657
}
643658
/* Now the other end has control, and we're just waiting on a

src/do.c

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,7 @@ checkpoint_level()
10911091
mtmp->female = poly_gender();
10921092
initedog(mtmp); /* mark as tame */
10931093
mtmp->mtame = 127;
1094+
mtmp->mlstmv = monstermoves; /* indicate what this level's "local time" is */
10941095
/* Setting the movement value is very important, so that the other
10951096
games know whether they should yield to us this turn. */
10961097
mtmp->movement = youmonst.movement;
@@ -1157,6 +1158,123 @@ uncheckpoint_level()
11571158
return 0; /* successful */
11581159
}
11591160

1161+
/* Looks at the current level, to deduce what its local time is. We
1162+
rely on the facts that a) nothing on the level can be dated in the
1163+
future, and b) any other players on the level are flagged with
1164+
their current times, so that if there are other players there, the
1165+
times will line up exactly (to within a turn, anyway). */
1166+
static long
1167+
calculate_objchain_local_time(ochain)
1168+
struct obj *ochain;
1169+
{
1170+
struct obj *otmp;
1171+
long localtime = 0L;
1172+
for (otmp = ochain; otmp; otmp = otmp->nobj) {
1173+
if (age_is_relative(otmp)) continue;
1174+
if (otmp->age > localtime) localtime = otmp->age;
1175+
/* Age has a different meaning inside ice boxes. */
1176+
if (Has_contents(otmp) && otmp->otyp != ICE_BOX) {
1177+
long t = calculate_objchain_local_time(otmp->cobj);
1178+
if (t > localtime) localtime = t;
1179+
}
1180+
}
1181+
return localtime;
1182+
}
1183+
static long
1184+
calculate_level_local_time()
1185+
{
1186+
struct monst *mtmp;
1187+
long localtime = -1L;
1188+
long t;
1189+
1190+
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
1191+
if (DEADMONSTER(mtmp)) continue;
1192+
if (mtmp->mlstmv > localtime) localtime = mtmp->mlstmv;
1193+
if (mtmp->minvent) {
1194+
t = calculate_objchain_local_time(mtmp->minvent);
1195+
if (t > localtime) localtime = t;
1196+
}
1197+
if (mtmp->mw && mtmp->mw->age > localtime) localtime = mtmp->mw->age;
1198+
}
1199+
t = calculate_objchain_local_time(fobj);
1200+
if (t > localtime) localtime = t;
1201+
t = calculate_objchain_local_time(level.buriedobjlist, FALSE);
1202+
if (t > localtime) localtime = t;
1203+
if (localtime == -1) {
1204+
/* no monsters or items on the level; this is rather unlikely, but
1205+
theoretically possible, and in this case we can choose the time
1206+
we like for the level's local time, so set it to our own local
1207+
time */
1208+
localtime = monstermoves;
1209+
}
1210+
return localtime;
1211+
}
1212+
1213+
static void
1214+
time_dilate_objchain(ochain, dtime)
1215+
struct obj *ochain;
1216+
long dtime;
1217+
{
1218+
struct obj *otmp;
1219+
for (otmp = ochain; otmp; otmp = otmp->nobj) {
1220+
if (age_is_relative(otmp)) continue;
1221+
otmp->age += dtime;
1222+
/* Age has a different meaning inside ice boxes. */
1223+
if (Has_contents(otmp) && otmp->otyp != ICE_BOX)
1224+
time_dilate_objchain(otmp->cobj, dtime);
1225+
}
1226+
}
1227+
static void
1228+
time_dilate_monchain(mchain, dtime)
1229+
struct monst *mchain;
1230+
long dtime;
1231+
{
1232+
struct monst *mtmp;
1233+
for (mtmp = mchain; mtmp; mtmp = mtmp->nmon) {
1234+
if (DEADMONSTER(mtmp)) continue;
1235+
mtmp->mlstmv += dtime;
1236+
if (mtmp->minvent) time_dilate_objchain(mtmp->minvent, dtime);
1237+
/* Assume monsters don't wield containers. */
1238+
if (mtmp->mw && !age_is_relative(mtmp->mw)) mtmp->mw->age += dtime;
1239+
}
1240+
}
1241+
1242+
/* Adjusts all timestamps saved in lockfile 0 by the given amount. */
1243+
static void
1244+
time_dilation(dtime)
1245+
long dtime;
1246+
{
1247+
/* nothing relevant in flags */
1248+
/* timestamps in struct you */
1249+
u.ucleansed += dtime;
1250+
u.usleep += dtime;
1251+
/* timers */
1252+
adjust_nonlocal_timers(dtime);
1253+
/* nothing relevant in light sources */
1254+
/* inventory */
1255+
time_dilate_objchain(invent, dtime);
1256+
/* migration */
1257+
time_dilate_objchain(migrating_objs, dtime);
1258+
time_dilate_monchain(migrating_mons, dtime);
1259+
/* nothing relevant in monster statistics */
1260+
/* nothing relevant in dungeons or level chains */
1261+
/* time handling */
1262+
moves += dtime;
1263+
monstermoves += dtime;
1264+
/* nothing relevant in quest status */
1265+
/* nothing relevant in spells (perhaps surprisingly, spell
1266+
knowledge is relative not absolute) */
1267+
/* nothing relevant in artifacts */
1268+
/* nothing relevant in oracularities */
1269+
/* stuck/steed are not multiplayer-safe, so needn't be adjusted */
1270+
/* nothing relevant in tutorial flags (TODO: the tutorial is currently
1271+
based on local time, when it should be based on character time,
1272+
but we can't fix that here) */
1273+
/* nothing relevant in character name */
1274+
/* nothing relevant in fruitnames */
1275+
/* nothing relevant in Plane of Water status */
1276+
}
1277+
11601278
#ifdef INSURANCE
11611279
void
11621280
save_currentstate()
@@ -1504,16 +1622,20 @@ boolean at_stairs, falling, portal;
15041622
(void) close(fd);
15051623
}
15061624

1507-
/* We must release the lock before anything that could print
1508-
messages, to avoid panicking other processes; the error
1509-
on level file removal above is OK, though, because it can
1510-
only happen if someone's gone around deleting files while
1511-
we have them locked, which would crash those processes
1512-
anyway. */
15131625
if (iflags.multiplayer) {
1626+
/* We must release the lock before anything that could print
1627+
messages, to avoid panicking other processes; the error
1628+
on level file removal above is OK, though, because it can
1629+
only happen if someone's gone around deleting files while
1630+
we have them locked, which would crash those processes
1631+
anyway. */
15141632
Strcpy(lock, iflags.mp_lock_name);
15151633
set_levelfile_name(lock, new_ledger);
15161634
unlock_file(lock);
1635+
1636+
/* We also need to adjust for local time, before doing
1637+
anything that might depend on timers. */
1638+
time_dilation(calculate_level_local_time() - monstermoves);
15171639
}
15181640

15191641
/* do this prior to level-change pline messages */

src/timeout.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* SCCS Id: @(#)timeout.c 3.4 2002/12/17 */
22
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3-
/* Modified 3 Jan 2011 by Alex Smith */
3+
/* Modified 4 Jul 2011 by Alex Smith */
44
/* NetHack may be freely redistributed. See license for details. */
55

66
#include "hack.h"
@@ -1868,6 +1868,35 @@ relink_timers(ghostly)
18681868
}
18691869
}
18701870

1871+
/* change the timeout time of all nonlocal timers by the given delta */
1872+
void
1873+
adjust_nonlocal_timers(dtime)
1874+
long dtime;
1875+
{
1876+
timer_element *prev, *curr, *ttmp, *nonlocals = 0;
1877+
1878+
/* unchain all nonlocal timers from the existing chain, and adjust
1879+
their timeouts */
1880+
for (prev = 0, curr = timer_base; curr; prev = curr, curr = curr->next) {
1881+
while (curr && !timer_is_local(curr)) {
1882+
if (!prev) timer_base = curr->next;
1883+
else prev->next = curr->next;
1884+
ttmp = curr;
1885+
curr = curr->next;
1886+
ttmp->next = nonlocals;
1887+
ttmp->timeout += dtime;
1888+
nonlocals = ttmp;
1889+
}
1890+
if (!curr) break;
1891+
}
1892+
1893+
/* then add them back to the original chain */
1894+
for (curr = nonlocals; curr; curr = ttmp) {
1895+
ttmp = curr->next;
1896+
insert_timer(curr);
1897+
}
1898+
}
1899+
18711900
#endif /* OVL0 */
18721901

18731902
/*timeout.c*/

0 commit comments

Comments
 (0)