Subject: [PATCH] YA speed system [LONG]
From: Michael Hedera <[email-masked]>
Date: 17 May 2001 14:33:25 -0000
Newsgroups: rec.games.roguelike.nethack

[This is a repost. I have never seen the original posting, even in
Doogle. Apologies if you see it twice]

Some time ago, I posted a patch modifying the NetHack speed system.
I made some improvements to it, and fixed some bugs. Here is a new
version.

A few comments on how (I hope) it works:

All the monsters and the player have their counters (mtmp->movement
and youmonst.movement, respectively). The game goes in little steps
(counted by mainclock), at each step decrementing the player's and
monters' counters (if positive) by TIME_STEP, by default 10. When a
monster's counter is <= 0, this monster will move: a variable
(mtmp->timet) is set to the base time taken by the move, then the
move happens, then mtmp->timet (randomized a bit) is added to the
monster's movement counter.

As I wanted to keep the patch relatively short, I left the general
layout of moveloop() unchanged, so the code still looks very
assymetrical; the part handling player's move is done "inside out".

The mtmp->timet can in principle be modified during the "move
happens". This should make it relatively straightforward to make
different actions take different amounts of time: just add:
  mtmp->timet = 3 * mtmp->timet / 5
or somesuch in the code performing given action.

The base time taken by a monster's move is NLENUNIT (default 1440)
divided by monster's speed; immobile creatures get a special
treatment.

The patch also uses flags.step, which is true if the last player's
move was... well... movement, not fight or something else. It is used
in allmain.c to (hopefully) fix the bug of steeds being speeded up by
the player wearing speed boots.

--- include/permonst.ori	Wed Jan 17 23:31:59 2001
+++ include/permonst.h	Tue May 15 10:03:43 2001
@@ -72,6 +72,11 @@
 #define FAST_SPEED 15
 #define VERY_FAST 24
 
+#define TIME_STEP 10	/* smallest time an action can take */
+#define NLENUNIT 1440	/* NetHack length unit */
+	/* NLENUNIT / NORMAL_SPEED is the time taken to move one step
+	   at normal speed */
+
 #define NON_PM		PM_PLAYERMON		/* "not a monster" */
 #define LOW_PM		(NON_PM+1)		/* first monster in mons[] */
 #define SPECIAL_PM	PM_LONG_WORM_TAIL	/* [normal] < ~ < [special] */
--- include/monst.ori	Thu Dec  7 21:47:04 2000
+++ include/monst.h	Tue May 15 10:03:43 2001
@@ -41,6 +41,7 @@
 	unsigned m_id;
 	short mnum;		/* permanent monster index number */
 	short movement;		/* movement points (derived from permonst definition and added effects */
+	short timet;		/* time taken by action */
 	uchar m_lev;		/* adjusted difficulty level of monster */
 	aligntyp malign;	/* alignment of this monster, relative to the
 				   player (positive = good to kill) */
--- include/flag.ori	Mon Dec 11 23:59:18 2000
+++ include/flag.h	Tue May 15 10:03:43 2001
@@ -53,6 +53,7 @@
 	boolean  mon_moving;	/* monsters' turn to move */
 	boolean  move;
 	boolean  mv;
+	boolean  step;		/* last move was a(n attempted) step */
 	boolean  nap;		/* `timed_delay' option for display effects */
 	boolean  nopick;	/* do not pickup objects (as when running) */
 	boolean  null;		/* OK to send nulls to the terminal */
--- src/allmain.ori	Sun Sep 10 23:59:49 2000
+++ src/allmain.c	Tue May 15 10:24:08 2001
@@ -1,4 +1,4 @@
-/*	SCCS Id: @(#)allmain.c	3.3	2000/05/05	*/
+/*	SCCS Id: @(#allmain.c	3.3	2000/05/05	*/
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -25,6 +25,7 @@
 #endif
     int moveamt = 0, wtcap = 0, change = 0;
     boolean didmove = FALSE, monscanmove = FALSE;
+    long mainclock;
 
     flags.moonphase = phase_of_the_moon();
     if(flags.moonphase == FULL_MOON) {
@@ -57,8 +58,13 @@
     (void) encumber_msg(); /* in case they auto-picked up something */
 
     u.uz0.dlevel = u.uz.dlevel;
-    youmonst.movement = NORMAL_SPEED;	/* give the hero some movement points */
+    youmonst.movement = 0;	/* the hero has the first move */
 
+    if (youmonst.data->mmove > 0)
+	youmonst.timet = NLENUNIT / youmonst.data->mmove;
+    else youmonst.timet = TIME_STEP;
+
+    mainclock = 0;
     for(;;) {
 #ifdef CLIPPING
 	cliparound(u.ux, u.uy);
@@ -71,81 +77,78 @@
 	didmove = flags.move;
 	if(didmove) {
 	    /* actual time passed */
-	    youmonst.movement -= NORMAL_SPEED;
+
+#ifdef STEED	    
+	    if (u.usteed && flags.step) {
+		/* your speed doesn't augment steed's speed */
+		youmonst.timet = mcalcmove(u.usteed);   /*  FIXME? */
+	    } else 
+#endif	    
+	    {
+		if (Very_fast) {
+		/* speed boots or potion */
+		/* average movement is 1.67 times normal */
+		youmonst.timet = (youmonst.timet*3)/5;
+		} else if (Fast) {
+		/* average movement is 1.33 times normal */
+		youmonst.timet = (youmonst.timet*3)/4;
+		}
+	    }
+
+	    /* currently encumberance slows all actions equally */
+	    switch (wtcap) {
+	    case UNENCUMBERED: break;
+		case SLT_ENCUMBER: youmonst.timet += youmonst.timet/3; break;
+		case MOD_ENCUMBER: youmonst.timet += youmonst.timet; break;
+		case HVY_ENCUMBER: youmonst.timet += youmonst.timet*3; break;
+		case EXT_ENCUMBER: youmonst.timet += youmonst.timet*7; break;
+		default: break;
+	    }
+     
+	    if (youmonst.data->mmove > 0)
+		youmonst.movement += youmonst.timet;
+	    else youmonst.movement = TIME_STEP;   /* low, but > 0 */
+
+	    settrack();
 
 	    do { /* hero can't move this turn loop */
+		struct monst *mtmp;
 		wtcap = encumber_msg();
-
+		
+		mainclock++;		
+		if ((youmonst.data->mmove > 0) && (youmonst.movement > 0))
+		    youmonst.movement -= TIME_STEP;
+		
 		flags.mon_moving = TRUE;
-		do {
-		    monscanmove = movemon();
-		    if (youmonst.movement > NORMAL_SPEED)
-			break;	/* it's now your turn */
-		} while (monscanmove);
+		movemon();
 		flags.mon_moving = FALSE;
 
-		if (!monscanmove && youmonst.movement < NORMAL_SPEED) {
-		    /* both you and the monsters are out of steam this round */
-		    /* set up for a new turn */
-		    struct monst *mtmp;
-		    mcalcdistress();	/* adjust monsters' trap, blind, etc */
-
-		    /* reallocate movement rations to monsters */
-		    for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
-			mtmp->movement += mcalcmove(mtmp);
+		for(mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
+		    if ((mtmp->data->mmove > 0) && (mtmp->movement > 0))
+			mtmp->movement -= TIME_STEP;
+		}
+
+		if ( !(mainclock % NORMAL_SPEED) ) {
+		    /**********************************/
+		    /* once per "turn" things go here */
+		    /**********************************/
 
+		    mcalcdistress();   /* adjust monsters' trap, blind, etc */
 		    if(!rn2(u.uevent.udemigod ? 25 :
 			    (depth(&u.uz) > depth(&stronghold_level)) ? 50 : 70))
 			(void) makemon((struct permonst *)0, 0, 0, NO_MM_FLAGS);
 
-		    /* calculate how much time passed. */
-#ifdef STEED
-		    if (u.usteed && flags.mv) {
-			/* your speed doesn't augment steed's speed */
-			moveamt = mcalcmove(u.usteed);
-		    } else
-#endif
-		    {
-			moveamt = youmonst.data->mmove;
-
-			if (Very_fast) {	/* speed boots or potion */
-			    /* average movement is 1.67 times normal */
-			    moveamt += NORMAL_SPEED / 2;
-			    if (rn2(3) == 0) moveamt += NORMAL_SPEED / 2;
-			} else if (Fast) {
-			    /* average movement is 1.33 times normal */
-			    if (rn2(3) != 0) moveamt += NORMAL_SPEED / 2;
-			}
-		    }
-
-		    switch (wtcap) {
-			case UNENCUMBERED: break;
-			case SLT_ENCUMBER: moveamt -= (moveamt / 4); break;
-			case MOD_ENCUMBER: moveamt -= (moveamt / 2); break;
-			case HVY_ENCUMBER: moveamt -= ((moveamt * 3) / 4); break;
-			case EXT_ENCUMBER: moveamt -= ((moveamt * 7) / 8); break;
-			default: break;
-		    }
-
-		    youmonst.movement += moveamt;
-		    if (youmonst.movement < 0) youmonst.movement = 0;
-		    settrack();
-
 		    monstermoves++;
 		    moves++;
 
-		    /********************************/
-		    /* once-per-turn things go here */
-		    /********************************/
-
 		    if(Glib) glibr();
 		    nh_timeout();
 		    run_regions();
-
+	
 		    if (u.ublesscnt)  u.ublesscnt--;
 		    if(flags.time && !flags.run)
 			flags.botl = 1;
-
+	
 		    /* One possible result of prayer is healing.  Whether or
 		     * not you get healed depends on your current hit points.
 		     * If you are allowed to regenerate during the prayer, the
@@ -274,12 +277,17 @@
 			    unmul((char *)0);
 		    }
 		}			
-	    } while (youmonst.movement<NORMAL_SPEED); /* hero can't move loop */
+	    } while (youmonst.movement > 0); /* hero can't move loop */
 
 	    /******************************************/
 	    /* once-per-hero-took-time things go here */
 	    /******************************************/
 
+	    /* set base duration for next move */
+	    /* youmonst.timet can be modified when moving */
+	    if (youmonst.data->mmove > 0)
+		youmonst.timet = NLENUNIT / youmonst.data->mmove;
+	    else youmonst.timet = TIME_STEP;
 
 	} /* actual time passed */
 
@@ -364,6 +372,7 @@
 	    sanity_check();
 #endif
 
+	flags.step = 0;
 	u.umoved = FALSE;
 
 	if (multi > 0) {
--- src/mon.ori	Sun Sep 10 23:59:49 2000
+++ src/mon.c	Tue May 15 10:24:08 2001
@@ -401,7 +401,8 @@
     }
 #endif
 
-    return mmove;
+    if (mmove > 0) return NLENUNIT / mmove;
+    else return TIME_STEP;   /* low, but > 0 */
 }
 
 /* actions that happen once per ``turn'', regardless of each
@@ -439,7 +440,6 @@
 movemon()
 {
     register struct monst *mtmp, *nmtmp;
-    register boolean somebody_can_move = FALSE;
 #if 0
     /* part of the original warning code which was replaced in 3.3.1 */
     warnlevel = 0;
@@ -470,22 +470,21 @@
 	/* Find a monster that we have not treated yet.	 */
 	if(DEADMONSTER(mtmp))
 	    continue;
-	if(mtmp->movement < NORMAL_SPEED)
+	if(mtmp->movement > 0)    /* not mtmp's time yet */
 	    continue;
 
-	mtmp->movement -= NORMAL_SPEED;
-	if (mtmp->movement >= NORMAL_SPEED)
-	    somebody_can_move = TRUE;
+	mtmp->timet = mcalcmove(mtmp);	/* may be overwritten when moving */
+	if (!mtmp->data->mmove) continue;	/* immobile */
 
-	if (minwater(mtmp)) continue;
+	if (minwater(mtmp)) goto admove;
 
 	if (is_hider(mtmp->data)) {
 	    /* unwatched mimics and piercers may hide again  [MRS] */
-	    if(restrap(mtmp))   continue;
+	    if(restrap(mtmp))   goto admove;
 	    if(mtmp->m_ap_type == M_AP_FURNITURE ||
 				mtmp->m_ap_type == M_AP_OBJECT)
-		    continue;
-	    if(mtmp->mundetected) continue;
+		    goto admove;
+	    if(mtmp->mundetected) goto admove;
 	}
 
 	/* continue if the monster died fighting */
@@ -501,10 +500,12 @@
 	    if (couldsee(mtmp->mx,mtmp->my) &&
 		(distu(mtmp->mx,mtmp->my) <= BOLT_LIM*BOLT_LIM) &&
 							fightm(mtmp))
-		continue;	/* mon might have died */
+		goto admove;	/* mon might have died */
 	}
 	if(dochugw(mtmp))		/* otherwise just move the monster */
-	    continue;
+	    continue;			/* dead don't need movement */
+admove: if (mtmp->data->mmove > 0)
+	    mtmp->movement += 2*mtmp->timet/3 + rn2(1+2*mtmp->timet/3);
     }
 #if 0
     /* part of the original warning code which was replaced in 3.3.1 */
@@ -520,10 +521,9 @@
     if (u.utotype) {
 	deferred_goto();
 	/* changed levels, so these monsters are dormant */
-	somebody_can_move = FALSE;
     }
 
-    return somebody_can_move;
+    return 0;
 }
 
 #endif /* OVL1 */
--- src/monmove.ori	Sat Mar 10 01:53:37 2001
+++ src/monmove.c	Tue May 15 10:24:08 2001
@@ -912,8 +912,8 @@
 		    return 2;
 
 		if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED)  &&
-		    rn2(4) && mtmp2->movement >= NORMAL_SPEED) {
-		    mtmp2->movement -= NORMAL_SPEED;
+		    rn2(4) && mtmp2->movement < mcalcmove(mtmp2)) {
+		    mtmp2->movement += mcalcmove(mtmp2);
 		    notonhead = 0;
 		    mstatus = mattackm(mtmp2, mtmp);	/* return attack */
 		    if (mstatus & MM_DEF_DIED)
--- src/hack.ori	Tue Dec 12 00:09:26 2000
+++ src/hack.c	Tue May 15 10:24:08 2001
@@ -745,7 +745,11 @@
 	    unmap_object(x, y);
 	    newsym(x, y);
 	}
+
 	/* not attacking an animal, so we try to move */
+
+	flags.step = 1;
+
 #ifdef STEED
 	if (u.usteed && !u.usteed->mcanmove && (u.dx || u.dy)) {
 		pline("%s won't move!", Monnam(u.usteed));