limits.c
1 /* ************************************************************************ 2 * File: limits.c Part of CircleMUD * 3 * Usage: limits & gain funcs for HMV, exp, hunger/thirst, idle time * 4 * * 5 * All rights reserved. See license.doc for complete information. * 6 * * 7 * Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University * 8 * CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * 9 ************************************************************************ */ 10 11 #include "conf.h" 12 #include "sysdep.h" 13 14 #include "structs.h" 15 #include "utils.h" 16 #include "spells.h" 17 #include "comm.h" 18 #include "db.h" 19 #include "handler.h" 20 #include "interpreter.h" 21 22 23 /* external variables */ 24 extern int max_exp_gain; 25 extern int max_exp_loss; 26 extern int idle_rent_time; 27 extern int idle_max_level; 28 extern int idle_void; 29 extern int immort_level_ok; 30 extern int use_autowiz; 31 extern int min_wizlist_lev; 32 extern int free_rent; 33 34 /* local functions */ 35 int graf(int grafage, int p0, int p1, int p2, int p3, int p4, int p5, int p6); 36 void run_autowiz(void); 37 38 void Crash_rentsave(struct char_data *ch, int cost); 39 int level_exp(int chclass, int level); 40 char *title_male(int chclass, int level); 41 char *title_female(int chclass, int level); 42 void update_char_objects(struct char_data *ch); /* handler.c */ 43 void reboot_wizlists(void); 44 45 /* When age < 15 return the value p0 */ 46 /* When age in 15..29 calculate the line between p1 & p2 */ 47 /* When age in 30..44 calculate the line between p2 & p3 */ 48 /* When age in 45..59 calculate the line between p3 & p4 */ 49 /* When age in 60..79 calculate the line between p4 & p5 */ 50 /* When age >= 80 return the value p6 */ 51 int graf(int grafage, int p0, int p1, int p2, int p3, int p4, int p5, int p6) 52 { 53 54 if (grafage < 15) 55 return (p0); /* < 15 */ 56 else if (grafage <= 29) 57 return (p1 + (((grafage - 15) * (p2 - p1)) / 15)); /* 15..29 */ 58 else if (grafage <= 44) 59 return (p2 + (((grafage - 30) * (p3 - p2)) / 15)); /* 30..44 */ 60 else if (grafage <= 59) 61 return (p3 + (((grafage - 45) * (p4 - p3)) / 15)); /* 45..59 */ 62 else if (grafage <= 79) 63 return (p4 + (((grafage - 60) * (p5 - p4)) / 20)); /* 60..79 */ 64 else 65 return (p6); /* >= 80 */ 66 } 67 68 69 /* 70 * The hit_limit, mana_limit, and move_limit functions are gone. They 71 * added an unnecessary level of complexity to the internal structure, 72 * weren't particularly useful, and led to some annoying bugs. From the 73 * players' point of view, the only difference the removal of these 74 * functions will make is that a character's age will now only affect 75 * the HMV gain per tick, and _not_ the HMV maximums. 76 */ 77 78 /* manapoint gain pr. game hour */ 79 int mana_gain(struct char_data *ch) 80 { 81 int gain; 82 83 if (IS_NPC(ch)) { 84 /* Neat and fast */ 85 gain = GET_LEVEL(ch); 86 } else { 87 gain = graf(age(ch)->year, 4, 8, 12, 16, 12, 10, 8); 88 89 /* Class calculations */ 90 91 /* Skill/Spell calculations */ 92 93 /* Position calculations */ 94 switch (GET_POS(ch)) { 95 case POS_SLEEPING: 96 gain *= 2; 97 break; 98 case POS_RESTING: 99 gain += (gain / 2); /* Divide by 2 */ 100 break; 101 case POS_SITTING: 102 gain += (gain / 4); /* Divide by 4 */ 103 break; 104 } 105 106 if (IS_MAGIC_USER(ch) || IS_CLERIC(ch)) 107 gain *= 2; 108 109 if ((GET_COND(ch, FULL) == 0) || (GET_COND(ch, THIRST) == 0)) 110 gain /= 4; 111 } 112 113 if (AFF_FLAGGED(ch, AFF_POISON)) 114 gain /= 4; 115 116 return (gain); 117 } 118 119 120 /* Hitpoint gain pr. game hour */ 121 int hit_gain(struct char_data *ch) 122 { 123 int gain; 124 125 if (IS_NPC(ch)) { 126 /* Neat and fast */ 127 gain = GET_LEVEL(ch); 128 } else { 129 130 gain = graf(age(ch)->year, 8, 12, 20, 32, 16, 10, 4); 131 132 /* Class/Level calculations */ 133 134 /* Skill/Spell calculations */ 135 136 /* Position calculations */ 137 138 switch (GET_POS(ch)) { 139 case POS_SLEEPING: 140 gain += (gain / 2); /* Divide by 2 */ 141 break; 142 case POS_RESTING: 143 gain += (gain / 4); /* Divide by 4 */ 144 break; 145 case POS_SITTING: 146 gain += (gain / 8); /* Divide by 8 */ 147 break; 148 } 149 150 if (IS_MAGIC_USER(ch) || IS_CLERIC(ch)) 151 gain /= 2; /* Ouch. */ 152 153 if ((GET_COND(ch, FULL) == 0) || (GET_COND(ch, THIRST) == 0)) 154 gain /= 4; 155 } 156 157 if (AFF_FLAGGED(ch, AFF_POISON)) 158 gain /= 4; 159 160 return (gain); 161 } 162 163 164 165 /* move gain pr. game hour */ 166 int move_gain(struct char_data *ch) 167 { 168 int gain; 169 170 if (IS_NPC(ch)) { 171 /* Neat and fast */ 172 gain = GET_LEVEL(ch); 173 } else { 174 gain = graf(age(ch)->year, 16, 20, 24, 20, 16, 12, 10); 175 176 /* Class/Level calculations */ 177 178 /* Skill/Spell calculations */ 179 180 181 /* Position calculations */ 182 switch (GET_POS(ch)) { 183 case POS_SLEEPING: 184 gain += (gain / 2); /* Divide by 2 */ 185 break; 186 case POS_RESTING: 187 gain += (gain / 4); /* Divide by 4 */ 188 break; 189 case POS_SITTING: 190 gain += (gain / 8); /* Divide by 8 */ 191 break; 192 } 193 194 if ((GET_COND(ch, FULL) == 0) || (GET_COND(ch, THIRST) == 0)) 195 gain /= 4; 196 } 197 198 if (AFF_FLAGGED(ch, AFF_POISON)) 199 gain /= 4; 200 201 return (gain); 202 } 203 204 205 206 void set_title(struct char_data *ch, char *title) 207 { 208 if (title == NULL) { 209 if (GET_SEX(ch) == SEX_FEMALE) 210 title = title_female(GET_CLASS(ch), GET_LEVEL(ch)); 211 else 212 title = title_male(GET_CLASS(ch), GET_LEVEL(ch)); 213 } 214 215 if (strlen(title) > MAX_TITLE_LENGTH) 216 title[MAX_TITLE_LENGTH] = '\0'; 217 218 if (GET_TITLE(ch) != NULL) 219 free(GET_TITLE(ch)); 220 221 GET_TITLE(ch) = strdup(title); 222 } 223 224 225 void run_autowiz(void) 226 { 227 #if defined(CIRCLE_UNIX) || defined(CIRCLE_WINDOWS) 228 if (use_autowiz) { 229 size_t res; 230 char buf[256]; 231 232 #if defined(CIRCLE_UNIX) 233 res = snprintf(buf, sizeof(buf), "nice ../bin/autowiz %d %s %d %s %d &", 234 min_wizlist_lev, WIZLIST_FILE, LVL_IMMORT, IMMLIST_FILE, (int) getpid()); 235 #elif defined(CIRCLE_WINDOWS) 236 res = snprintf(buf, sizeof(buf), "autowiz %d %s %d %s", 237 min_wizlist_lev, WIZLIST_FILE, LVL_IMMORT, IMMLIST_FILE); 238 #endif /* CIRCLE_WINDOWS */ 239 240 /* Abusing signed -> unsigned conversion to avoid '-1' check. */ 241 if (res < sizeof(buf)) { 242 mudlog(CMP, LVL_IMMORT, FALSE, "Initiating autowiz."); 243 system(buf); 244 reboot_wizlists(); 245 } else 246 log("Cannot run autowiz: command-line doesn't fit in buffer."); 247 } 248 #endif /* CIRCLE_UNIX || CIRCLE_WINDOWS */ 249 } 250 251 252 253 void gain_exp(struct char_data *ch, int gain) 254 { 255 int is_altered = FALSE; 256 int num_levels = 0; 257 258 if (!IS_NPC(ch) && ((GET_LEVEL(ch) < 1 || GET_LEVEL(ch) >= LVL_IMMORT))) 259 return; 260 261 if (IS_NPC(ch)) { 262 GET_EXP(ch) += gain; 263 return; 264 } 265 if (gain > 0) { 266 gain = MIN(max_exp_gain, gain); /* put a cap on the max gain per kill */ 267 GET_EXP(ch) += gain; 268 while (GET_LEVEL(ch) < LVL_IMMORT - immort_level_ok && 269 GET_EXP(ch) >= level_exp(GET_CLASS(ch), GET_LEVEL(ch) + 1)) { 270 GET_LEVEL(ch) += 1; 271 num_levels++; 272 advance_level(ch); 273 is_altered = TRUE; 274 } 275 276 if (is_altered) { 277 mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s advanced %d level%s to level %d.", 278 GET_NAME(ch), num_levels, num_levels == 1 ? "" : "s", GET_LEVEL(ch)); 279 if (num_levels == 1) 280 send_to_char(ch, "You rise a level!\r\n"); 281 else 282 send_to_char(ch, "You rise %d levels!\r\n", num_levels); 283 set_title(ch, NULL); 284 if (GET_LEVEL(ch) >= LVL_IMMORT) 285 run_autowiz(); 286 } 287 } else if (gain < 0) { 288 gain = MAX(-max_exp_loss, gain); /* Cap max exp lost per death */ 289 GET_EXP(ch) += gain; 290 if (GET_EXP(ch) < 0) 291 GET_EXP(ch) = 0; 292 } 293 } 294 295 296 void gain_exp_regardless(struct char_data *ch, int gain) 297 { 298 int is_altered = FALSE; 299 int num_levels = 0; 300 301 GET_EXP(ch) += gain; 302 if (GET_EXP(ch) < 0) 303 GET_EXP(ch) = 0; 304 305 if (!IS_NPC(ch)) { 306 while (GET_LEVEL(ch) < LVL_IMPL && 307 GET_EXP(ch) >= level_exp(GET_CLASS(ch), GET_LEVEL(ch) + 1)) { 308 GET_LEVEL(ch) += 1; 309 num_levels++; 310 advance_level(ch); 311 is_altered = TRUE; 312 } 313 314 if (is_altered) { 315 mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s advanced %d level%s to level %d.", 316 GET_NAME(ch), num_levels, num_levels == 1 ? "" : "s", GET_LEVEL(ch)); 317 if (num_levels == 1) 318 send_to_char(ch, "You rise a level!\r\n"); 319 else 320 send_to_char(ch, "You rise %d levels!\r\n", num_levels); 321 set_title(ch, NULL); 322 if (GET_LEVEL(ch) >= LVL_IMMORT) 323 run_autowiz(); 324 } 325 } 326 } 327 328 329 void gain_condition(struct char_data *ch, int condition, int value) 330 { 331 bool intoxicated; 332 333 if (IS_NPC(ch) || GET_COND(ch, condition) == -1) /* No change */ 334 return; 335 336 intoxicated = (GET_COND(ch, DRUNK) > 0); 337 338 GET_COND(ch, condition) += value; 339 340 GET_COND(ch, condition) = MAX(0, GET_COND(ch, condition)); 341 GET_COND(ch, condition) = MIN(24, GET_COND(ch, condition)); 342 343 if (GET_COND(ch, condition) || PLR_FLAGGED(ch, PLR_WRITING)) 344 return; 345 346 switch (condition) { 347 case FULL: 348 send_to_char(ch, "You are hungry.\r\n"); 349 break; 350 case THIRST: 351 send_to_char(ch, "You are thirsty.\r\n"); 352 break; 353 case DRUNK: 354 if (intoxicated) 355 send_to_char(ch, "You are now sober.\r\n"); 356 break; 357 default: 358 break; 359 } 360 361 } 362 363 364 void check_idling(struct char_data *ch) 365 { 366 if (++(ch->char_specials.timer) > idle_void) { 367 if (GET_WAS_IN(ch) == NOWHERE && IN_ROOM(ch) != NOWHERE) { 368 GET_WAS_IN(ch) = IN_ROOM(ch); 369 if (FIGHTING(ch)) { 370 stop_fighting(FIGHTING(ch)); 371 stop_fighting(ch); 372 } 373 act("$n disappears into the void.", TRUE, ch, 0, 0, TO_ROOM); 374 send_to_char(ch, "You have been idle, and are pulled into a void.\r\n"); 375 save_char(ch); 376 Crash_crashsave(ch); 377 char_from_room(ch); 378 char_to_room(ch, 1); 379 } else if (ch->char_specials.timer > idle_rent_time) { 380 if (IN_ROOM(ch) != NOWHERE) 381 char_from_room(ch); 382 char_to_room(ch, 3); 383 if (ch->desc) { 384 STATE(ch->desc) = CON_DISCONNECT; 385 /* 386 * For the 'if (d->character)' test in close_socket(). 387 * -gg 3/1/98 (Happy anniversary.) 388 */ 389 ch->desc->character = NULL; 390 ch->desc = NULL; 391 } 392 if (free_rent) 393 Crash_rentsave(ch, 0); 394 else 395 Crash_idlesave(ch); 396 mudlog(CMP, LVL_GOD, TRUE, "%s force-rented and extracted (idle).", GET_NAME(ch)); 397 extract_char(ch); 398 } 399 } 400 } 401 402 403 404 /* Update PCs, NPCs, and objects */ 405 void point_update(void) 406 { 407 struct char_data *i, *next_char; 408 struct obj_data *j, *next_thing, *jj, *next_thing2; 409 410 /* characters */ 411 for (i = character_list; i; i = next_char) { 412 next_char = i->next; 413 414 gain_condition(i, FULL, -1); 415 gain_condition(i, DRUNK, -1); 416 gain_condition(i, THIRST, -1); 417 418 if (GET_POS(i) >= POS_STUNNED) { 419 GET_HIT(i) = MIN(GET_HIT(i) + hit_gain(i), GET_MAX_HIT(i)); 420 GET_MANA(i) = MIN(GET_MANA(i) + mana_gain(i), GET_MAX_MANA(i)); 421 GET_MOVE(i) = MIN(GET_MOVE(i) + move_gain(i), GET_MAX_MOVE(i)); 422 if (AFF_FLAGGED(i, AFF_POISON)) 423 if (damage(i, i, 2, SPELL_POISON) == -1) 424 continue; /* Oops, they died. -gg 6/24/98 */ 425 if (GET_POS(i) <= POS_STUNNED) 426 update_pos(i); 427 } else if (GET_POS(i) == POS_INCAP) { 428 if (damage(i, i, 1, TYPE_SUFFERING) == -1) 429 continue; 430 } else if (GET_POS(i) == POS_MORTALLYW) { 431 if (damage(i, i, 2, TYPE_SUFFERING) == -1) 432 continue; 433 } 434 if (!IS_NPC(i)) { 435 update_char_objects(i); 436 if (GET_LEVEL(i) < idle_max_level) 437 check_idling(i); 438 } 439 } 440 441 /* objects */ 442 for (j = object_list; j; j = next_thing) { 443 next_thing = j->next; /* Next in object list */ 444 445 /* If this is a corpse */ 446 if (IS_CORPSE(j)) { 447 /* timer count down */ 448 if (GET_OBJ_TIMER(j) > 0) 449 GET_OBJ_TIMER(j)--; 450 451 if (!GET_OBJ_TIMER(j)) { 452 453 if (j->carried_by) 454 act("$p decays in your hands.", FALSE, j->carried_by, j, 0, TO_CHAR); 455 else if ((IN_ROOM(j) != NOWHERE) && (world[IN_ROOM(j)].people)) { 456 act("A quivering horde of maggots consumes $p.", 457 TRUE, world[IN_ROOM(j)].people, j, 0, TO_ROOM); 458 act("A quivering horde of maggots consumes $p.", 459 TRUE, world[IN_ROOM(j)].people, j, 0, TO_CHAR); 460 } 461 for (jj = j->contains; jj; jj = next_thing2) { 462 next_thing2 = jj->next_content; /* Next in inventory */ 463 obj_from_obj(jj); 464 465 if (j->in_obj) 466 obj_to_obj(jj, j->in_obj); 467 else if (j->carried_by) 468 obj_to_room(jj, IN_ROOM(j->carried_by)); 469 else if (IN_ROOM(j) != NOWHERE) 470 obj_to_room(jj, IN_ROOM(j)); 471 else 472 core_dump(); 473 } 474 extract_obj(j); 475 } 476 } 477 } 478 }