""" #************************************************************************** * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, * * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. * * * * Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael * * Chastain, Michael Quan, and Mitchell Tse. * * * * In order to use any part of this Merc Diku Mud, you must comply with * * both the original Diku license in 'license.doc' as well the Merc * * license in 'license.txt'. In particular, you may not remove either of * * these copyright notices. * * * * Much time and thought has gone into this software and you are * * benefitting. We hope that you share your changes too. What goes * * around, comes around. * ***************************************************************************/ #************************************************************************** * ROM 2.4 is copyright 1993-1998 Russ Taylor * * ROM has been brought to you by the ROM consortium * * Russ Taylor (rtaylor@hypercube.org) * * Gabrielle Taylor (gtaylor@hypercube.org) * * Brian Moore (zump@rom.org) * * By using this code, you have agreed to follow the terms of the * * ROM license, in the file Rom24/doc/rom.license * ***************************************************************************/ #*********** * Ported to Python by Davion of MudBytes.net * Using Miniboa https://code.google.com/p/miniboa/ * Now using Python 3 version https://code.google.com/p/miniboa-py3/ ************/ """ from merc import * from db import area_update from handler import * from save import save_char_obj import skills import const import fight import act_move # * Advancement stuff. def advance_level( ch, hide ): ch.pcdata.last_level = ( ch.played + (int) (current_time - ch.logon) ) // 3600 buf = "the %s" % ( const.title_table [ch.guild.name] [ch.level] [1 if ch.sex == SEX_FEMALE else 0] ) set_title( ch, buf ) add_hp = const.con_app[ch.get_curr_stat(STAT_CON)].hitp + random.randint( ch.guild.hp_min, ch.guild.hp_max ) add_mana = random.randint( 2, (2*ch.get_curr_stat(STAT_INT) + ch.get_curr_stat(STAT_WIS)) // 5) if not ch.guild.fMana: add_mana //= 2 add_move = random.randint( 1, (ch.get_curr_stat(STAT_CON) + ch.get_curr_stat(STAT_DEX)) // 6 ) add_prac = const.wis_app[ch.get_curr_stat(STAT_WIS)].practice add_hp = add_hp * 9 // 10 add_mana = add_mana * 9 // 10 add_move = add_move * 9 // 10 add_hp = max(2, add_hp) add_mana = max(2, add_mana) add_move = max(6, add_move) ch.max_hit += add_hp ch.max_mana += add_mana ch.max_move += add_move ch.practice += add_prac ch.train += 1 ch.pcdata.perm_hit += add_hp ch.pcdata.perm_mana += add_mana ch.pcdata.perm_move += add_move if not hide: ch.send("You gain %d hit point%s, %d mana, %d move, and %d practice%s.\n" % ( add_hp, "" if add_hp == 1 else "s", add_mana, add_move, add_prac, "" if add_prac == 1 else "s") ) def gain_exp( ch, gain ): if IS_NPC(ch) or ch.level >= LEVEL_HERO: return ch.exp = max( ch.exp_per_level(ch.pcdata.points), ch.exp + gain ) while ch.level < LEVEL_HERO and ch.exp >= ch.exp_per_level(ch.pcdata.points) * (ch.level+1): ch.send("You raise a level!! ") ch.level += 1 print ("%s gained level %d\r\n" % (ch.name,ch.level)) wiznet("$N has attained level %d!" % ch.level,ch,None,WIZ_LEVELS,0,0) advance_level(ch,False) save_char_obj(ch) # * Regeneration stuff. def hit_gain( ch ): if not ch.in_room: return 0 if IS_NPC(ch): gain = 5 + ch.level if IS_AFFECTED(ch,AFF_REGENERATION): gain *= 2 if ch.position == POS_SLEEPING: gain = 3 * gain // 2 elif ch.position == POS_RESTING: pass elif ch.position == POS_FIGHTING: gain //= 3 else: gain //= 2 else: gain = max(3,ch.get_curr_stat(STAT_CON) - 3 + ch.level // 2) gain += ch.guild.hp_max - 10 number = random.randint(1,99) if number < ch.get_skill('fast healing'): gain += number * gain // 100 if ch.hit < ch.max_hit: skills.check_improve(ch,'fast healing',True,8) if ch.position == POS_SLEEPING: pass elif ch.position == POS_RESTING: gain //= 2 elif ch.position == POS_FIGHTING: gain //= 6 else: gain //= 4 if not ch.pcdata.condition[COND_HUNGER]: gain //= 2 if not ch.pcdata.condition[COND_THIRST]: gain //= 2 gain = gain * ch.in_room.heal_rate // 100 if ch.on and ch.on.item_type == ITEM_FURNITURE: gain = gain * ch.on.value[3] // 100 if IS_AFFECTED(ch, AFF_POISON): gain //= 4 if IS_AFFECTED(ch, AFF_PLAGUE): gain //= 8 if IS_AFFECTED(ch,AFF_HASTE) or IS_AFFECTED(ch,AFF_SLOW): gain //=2 return int(min(gain, ch.max_hit - ch.hit)) def mana_gain( ch ): if ch.in_room == None: return 0 if IS_NPC(ch): gain = 5 + ch.level if ch.position == POS_SLEEPING: 3 * gain // 2 elif ch.position == POS_RESTING: pass elif ch.position == POS_FIGHTING: gain //= 3 else: gain //= 2 else: gain = (ch.get_curr_stat(STAT_WIS) + ch.get_curr_stat(STAT_INT) + ch.level) // 2 number = random.randint(1,99) if number < ch.get_skill('meditation'): gain += number * gain // 100 if ch.mana < ch.max_mana: skills.check_improve(ch,'meditation',True,8) if not ch.guild.fMana: gain //= 2 if ch.position == POS_SLEEPING: pass elif ch.position == POS_RESTING: gain //= 2 elif ch.position == POS_FIGHTING: gain //= 6 else: gain //= 4 if not ch.pcdata.condition[COND_HUNGER]: gain //= 2 if not ch.pcdata.condition[COND_THIRST]: gain //= 2 gain = gain * ch.in_room.mana_rate // 100 if ch.on and ch.on.item_type == ITEM_FURNITURE: gain = gain * ch.on.value[4] // 100 if IS_AFFECTED( ch, AFF_POISON ): gain //= 4 if IS_AFFECTED(ch, AFF_PLAGUE): gain //= 8 if IS_AFFECTED(ch,AFF_HASTE) or IS_AFFECTED(ch,AFF_SLOW): gain //=2 return int(min(gain, ch.max_mana - ch.mana)) def move_gain( ch ): if not ch.in_room: return 0 if IS_NPC(ch): gain = ch.level else: gain = max( 15, ch.level ) if ch.position == POS_SLEEPING: gain += ch.get_curr_stat(STAT_DEX) elif ch.position == POS_RESTING: gain += ch.get_curr_stat(STAT_DEX) // 2 if not ch.pcdata.condition[COND_HUNGER]: gain //= 2 if not ch.pcdata.condition[COND_THIRST]: gain //= 2 gain = gain * ch.in_room.heal_rate // 100 if ch.on and ch.on.item_type == ITEM_FURNITURE: gain = gain * ch.on.value[3] // 100 if IS_AFFECTED(ch, AFF_POISON): gain //= 4 if IS_AFFECTED(ch, AFF_PLAGUE): gain //= 8 if IS_AFFECTED(ch,AFF_HASTE) or IS_AFFECTED(ch,AFF_SLOW): gain //=2 return int(min(gain, ch.max_move - ch.move)) def gain_condition( ch, iCond, value ): if value == 0 or IS_NPC(ch) or ch.level >= LEVEL_IMMORTAL: return condition = ch.pcdata.condition[iCond] if condition == -1: return ch.pcdata.condition[iCond] = max(0, min(condition + value, 48)) if ch.pcdata.condition[iCond] == 0: if iCond == COND_HUNGER: ch.send("You are hungry.\n") elif iCond == COND_THIRST: ch.send("You are thirsty.\n") elif iCond == COND_DRUNK: if condition != 0: ch.send("You are sober.\n") # * Mob autonomous action. # * This function takes 25% to 35% of ALL Merc cpu time. # * -- Furey def mobile_update( ): # Examine all mobs. */ for ch in char_list[:]: if not IS_NPC(ch) or ch.in_room == None or IS_AFFECTED(ch,AFF_CHARM): continue if ch.in_room.area.empty and not IS_SET(ch.act,ACT_UPDATE_ALWAYS): continue # Examine call for special procedure */ if ch.spec_fun: if ch.spec_fun( ch ): continue if ch.pIndexData.pShop: # give him some gold */ if (ch.gold * 100 + ch.silver) < ch.pIndexData.wealth: ch.gold += ch.pIndexData.wealth * random.randint(1,20) // 5000000 ch.silver += ch.pIndexData.wealth * random.randint(1,20) // 50000 # That's all for sleeping / busy monster, and empty zones */ if ch.position != POS_STANDING: continue # Scavenge */ if IS_SET(ch.act, ACT_SCAVENGER) and ch.in_room.contents != None and random.randint(0,6) == 0 : top = 1 obj_best = 0 for obj in ch.in_room.contents: if CAN_WEAR(obj, ITEM_TAKE) and ch.can_loot(obj) and obj.cost > top and obj.cost > 0: obj_best = obj top = obj.cost if obj_best: obj_best.from_room() obj_best.to_char(ch) act("$n gets $p.", ch, obj_best, None, TO_ROOM) # Wander */ door = random.randint(0,5) pexit = ch.in_room.exit[door] if not IS_SET(ch.act, ACT_SENTINEL) \ and random.randint(0,3) == 0 \ and pexit \ and pexit.to_room \ and not IS_SET(pexit.exit_info, EX_CLOSED) \ and not IS_SET(pexit.to_room.room_flags, ROOM_NO_MOB) \ and (not IS_SET(ch.act, ACT_STAY_AREA) or pexit.to_room.area == ch.in_room.area) \ and (not IS_SET(ch.act, ACT_OUTDOORS) or not IS_SET(pexit.to_room.room_flags,ROOM_INDOORS)) \ and (not IS_SET(ch.act, ACT_INDOORS) \ or IS_SET(pexit.to_room.room_flags,ROOM_INDOORS)): act_move.move_char(ch, door, False) # # * Update the weather. def weather_update( ): buf = "" time_info.hour += 1 if time_info.hour == 5: weather_info.sunlight = SUN_LIGHT buf = "The day has begun.\n" elif time_info.hour == 6: weather_info.sunlight = SUN_RISE buf = "The sun rises in the east.\n" elif time_info.hour == 19: weather_info.sunlight = SUN_SET buf = "The sun slowly disappears in the west.\n" elif time_info.hour == 20: weather_info.sunlight = SUN_DARK buf = "The night has begun.\n" elif time_info.hour == 24: time_info.hour = 0 time_info.day += 1 if time_info.day >= 35: time_info.day = 0 time_info.month += 1 if time_info.month >= 17: time_info.month = 0 time_info.year += 1 # #* Weather change. if time_info.month >= 9 and time_info.month <= 16: diff = -2 if weather_info.mmhg > 985 else 2 else: diff = -2 if weather_info.mmhg > 1015 else 2 weather_info.change += diff * dice(1, 4) + dice(2, 6) - dice(2, 6) weather_info.change = max(weather_info.change, -12) weather_info.change = min(weather_info.change, 12) weather_info.mmhg += weather_info.change weather_info.mmhg = max(weather_info.mmhg, 960) weather_info.mmhg = min(weather_info.mmhg, 1040) if weather_info.sky == SKY_CLOUDLESS: if weather_info.mmhg < 990 or ( weather_info.mmhg < 1010 and random.randint(0, 2 ) == 0 ): buf += "The sky is getting cloudy.\n" weather_info.sky = SKY_CLOUDY elif weather_info.sky == SKY_CLOUDY: if weather_info.mmhg < 970 or ( weather_info.mmhg < 990 and random.randint(0, 2 ) == 0 ): buf += "It starts to rain.\n" weather_info.sky = SKY_RAINING if weather_info.mmhg > 1030 and random.randint(0, 2 ) == 0: buf += "The clouds disappear.\n" weather_info.sky = SKY_CLOUDLESS elif weather_info.sky == SKY_RAINING: if weather_info.mmhg < 970 and number_bits( 2 ) == 0: strcat( buf, "Lightning flashes in the sky.\n" ) weather_info.sky = SKY_LIGHTNING if weather_info.mmhg > 1030 or ( weather_info.mmhg > 1010 and random.randint(0, 2) == 0 ): strcat( buf, "The rain stopped.\n" ) weather_info.sky = SKY_CLOUDY elif weather_info.sky == SKY_LIGHTNING: if weather_info.mmhg > 1010 or ( weather_info.mmhg > 990 and random.randint(0, 2 ) == 0 ): strcat( buf, "The lightning has stopped.\n" ) weather_info.sky = SKY_RAINING else: print ("Bug: Weather_update: bad sky %d." % weather_info.sky) weather_info.sky = SKY_CLOUDLESS if buf: for d in descriptor_list: if d.is_connected(con_playing) and IS_OUTSIDE(d.character) and IS_AWAKE(d.character): ch.send(buf) return save_number = 0 # # * Update all chars, including mobs. def char_update( ): # update save counter */ global save_number save_number += 1 if save_number > 29: save_number = 0 ch_quit = [] for ch in char_list[:]: if ch.timer > 30: ch_quit.append(ch) if ch.position >= POS_STUNNED: # check to see if we need to go home */ if IS_NPC(ch) and ch.zone and ch.zone != ch.in_room.area \ and not ch.desc and not ch.fighting and not IS_AFFECTED(ch,AFF_CHARM) and random.randint(1,99) < 5: act("$n wanders on home.",ch,None,None,TO_ROOM) ch.extract(True) continue if ch.hit < ch.max_hit: ch.hit += hit_gain(ch) else: ch.hit = ch.max_hit if ch.mana < ch.max_mana: ch.mana += mana_gain(ch) else: ch.mana = ch.max_mana if ch.move < ch.max_move: ch.move += move_gain(ch) else: ch.move = ch.max_move if ch.position == POS_STUNNED: update_pos( ch ) if not IS_NPC(ch) and ch.level < LEVEL_IMMORTAL: obj = ch.get_eq(WEAR_LIGHT) if obj and obj.item_type == ITEM_LIGHT and obj.value[2] > 0: obj.value[2] -= 1 if obj.value[2] == 0 and ch.in_room != None: ch.in_room.light -= 1 act( "$p goes out.", ch, obj, None, TO_ROOM ) act( "$p flickers and goes out.", ch, obj, None, TO_CHAR ) obj.extract() elif obj.value[2] <= 5 and ch.in_room: act("$p flickers.",ch,obj,None,TO_CHAR) if IS_IMMORTAL(ch): ch.timer = 0 ch.timer += 1 if ch.timer >= 12: if not ch.was_in_room and ch.in_room: ch.was_in_room = ch.in_room if ch.fighting: stop_fighting( ch, True ) act( "$n disappears into the void.", ch, None, None, TO_ROOM ) ch.send("You disappear into the void.\n") if ch.level > 1: save_char_obj( ch ) ch.from_room() ch.to_room(room_index_hash[ROOM_VNUM_LIMBO]) gain_condition( ch, COND_DRUNK, -1 ) gain_condition( ch, COND_FULL, -4 if ch.size > SIZE_MEDIUM else -2 ) gain_condition( ch, COND_THIRST, -1 ) gain_condition( ch, COND_HUNGER, -2 if ch.size > SIZE_MEDIUM else -1) for paf in ch.affected[:]: if paf.duration > 0 : paf.duration -= 1 if random.randint(0,4) == 0 and paf.level > 0: paf.level -= 1 # spell strength fades with time */ elif paf.duration < 0: pass else: #multiple affects. don't send the spelldown msg multi = [a for a in ch.affected if a.type == paf.type and a is not paf and a.duration > 0] if not multi and paf.type > 0 and skill_table[paf.type].msg_off: ch.send(skill_table[paf.type].msg_off+"\n") ch.affect_remove(paf) # #* Careful with the damages here, #* MUST NOT refer to ch after damage taken, #* as it may be lethal damage (on NPC). #*/ if is_affected(ch, 'plague') and ch: if ch.in_room == None: continue act("$n writhes in agony as plague sores erupt from $s skin.", ch,None,None,TO_ROOM) ch.send("You writhe in agony from the plague.\n") af = [a for a in ch.affected if af.type == 'plague'][:1] if not af: REMOVE_BIT(ch.affected_by,AFF_PLAGUE) continue if af.level == 1: continue plague = AFFECT_DATA() plague.where = TO_AFFECTS plague.type = gsn_plague plague.level = af.level - 1 plague.duration = random.randint(1, 2 * plague.level) plague.location = APPLY_STR plague.modifier =-5 plague.bitvector = AFF_PLAGUE for vch in ch.in_room.people: if not saves_spell(plague.level - 2,vch,DAM_DISEASE) and not IS_IMMORTAL(vch) \ and not IS_AFFECTED(vch, AFF_PLAGUE) and random.randint(0, 4) == 0: vch.send("You feel hot and feverish.\n") act("$n shivers and looks very ill.", vch, None, None, TO_ROOM) vch.affect_join(plague) dam = min(ch.level, af.level // 5 + 1) ch.mana -= dam ch.move -= dam damage(ch, ch, dam, gsn_plague, DAM_DISEASE, False) elif IS_AFFECTED(ch, AFF_POISON) and ch and not IS_AFFECTED(ch, AFF_SLOW): poison = affect_find(ch.affected,'poison') if poison: act("$n shivers and suffers.", ch, None, None, TO_ROOM) ch.send("You shiver and suffer.\n") damage(ch,ch,poison.level // 10 + 1,gsn_poison, DAM_POISON,False) elif ch.position == POS_INCAP and random.randint(0,1) == 0: damage(ch, ch, 1, TYPE_UNDEFINED, DAM_NONE, False) elif ch.position == POS_MORTAL: damage(ch, ch, 1, TYPE_UNDEFINED, DAM_NONE, False) # # * Autosave and autoquit. # * Check that these chars still exist. # */ for ch in char_list[:]: if ch.desc and save_number == 28: save_char_obj(ch) for ch in ch_quit[:]: ch.do_quit("") # # Update all objs. # This function is performance sensitive. # def obj_update( ): for obj in object_list[:]: # go through affects and decrement */ for paf in obj.affected[:]: if paf.duration > 0: paf.duration -= 1 if random.randint(0,4) == 0 and paf.level > 0: paf.level -= 1 # spell strength fades with time */ elif paf.duration < 0: pass else: multi = [a for a in obj.affected if a.type == paf.type and a is not paf and a.duration > 0] if multi and paf.type > 0 and skill_table[paf.type].msg_obj: if obj.carried_by: rch = obj.carried_by act(skill_table[paf.type].msg_obj, rch,obj,None,TO_CHAR) if obj.in_room != None and obj.in_room.people: act(skill_table[paf.type].msg_obj, obj.in_room.people ,obj,None,TO_ALL) obj.affect_remove(paf) obj.timer -= 1 if obj.timer <= 0 or obj.timer > 0: continue if obj.item_type == ITEM_FOUNTAIN: message = "$p dries up." elif obj.item_type == ITEM_CORPSE_NPC: message = "$p decays into dust." elif obj.item_type == ITEM_CORPSE_PC: message = "$p decays into dust." elif obj.item_type == ITEM_FOOD: message = "$p decomposes." elif obj.item_type == ITEM_POTION: message = "$p has evaporated from disuse." elif obj.item_type == ITEM_PORTAL: message = "$p fades out of existence." elif obj.item_type == ITEM_CONTAINER: if CAN_WEAR(obj,ITEM_WEAR_FLOAT): if obj.contains: message = "$p flickers and vanishes, spilling its contents on the floor." else: message = "$p flickers and vanishes." else: message = "$p crumbles into dust." else: message = "$p crumbles into dust." if obj.carried_by: if IS_NPC(obj.carried_by) and obj.carried_by.pIndexData.pShop: obj.carried_by.silver += obj.cost // 5 else: act( message, obj.carried_by, obj, None, TO_CHAR ) if obj.wear_loc == WEAR_FLOAT: act(message,obj.carried_by,obj,None,TO_ROOM) elif obj.in_room and obj.in_room.people: if not (obj.in_obj and obj.in_obj.pIndexData.vnum == OBJ_VNUM_PIT and not CAN_WEAR(obj.in_obj,ITEM_TAKE)): act( message, obj.in_room.people[:1], obj, None, TO_ROOM ) act( message, obj.in_room.people[:1], obj, None, TO_CHAR ) if (obj.item_type == ITEM_CORPSE_PC or obj.wear_loc == WEAR_FLOAT) and obj.contains: # save the contents */ for t_obj in obj.contains[:]: t_obj.from_obj() if obj.in_obj: # in another object */ t_obj.to_obj(obj.in_obj) elif obj.carried_by: # carried */ if obj.wear_loc == WEAR_FLOAT: if obj.carried_by.in_room == None: t_obj.extract() else: t_obj.to_room(obj.carried_by.in_room) else: t_obj.to_char(obj.carried_by) elif not obj.in_room: # destroy it */ t_obj.extract() else: # to a room */ t_obj.to_room(obj.in_room) obj.extract() return # # * Aggress. # * # * for each mortal PC # * for each mob in room # * aggress on some random PC # * # * This function takes 25% to 35% of ALL Merc cpu time. # * Unfortunately, checking on each PC move is too tricky, # * because we don't the mob to just attack the first PC # * who leads the party into the room. # * # * -- Furey # */ def aggr_update( ): for wch in char_list[:]: if IS_NPC(wch) \ or wch.level >= LEVEL_IMMORTAL \ or wch.in_room == None \ or wch.in_room.area.empty: continue for ch in wch.in_room.people[:]: if not IS_NPC(ch) \ or not IS_SET(ch.act, ACT_AGGRESSIVE) \ or IS_SET(ch.in_room.room_flags,ROOM_SAFE) \ or IS_AFFECTED(ch,AFF_CALM) \ or ch.fighting != None \ or IS_AFFECTED(ch, AFF_CHARM) \ or not IS_AWAKE(ch) \ or ( IS_SET(ch.act, ACT_WIMPY) and IS_AWAKE(wch) ) \ or not ch.can_see(wch) \ or random.randint(0,1) == 0: continue # # * Ok we have a 'wch' player character and a 'ch' npc aggressor. # * Now make the aggressor fight a RANDOM pc victim in the room, # * giving each 'vch' an equal chance of selection. count = 0 victim = None for vch in wch.in_room.people[:]: if not IS_NPC(vch) \ and vch.level < LEVEL_IMMORTAL \ and ch.level >= vch.level - 5 \ and ( not IS_SET(ch.act, ACT_WIMPY) or not IS_AWAKE(vch) ) \ and ch.can_see(vch): if random.randint( 0, count ) == 0: victim = vch count += 1 if not victim: continue multi_hit( ch, victim, TYPE_UNDEFINED ) # # * Handle all kinds of updates. # * Called once per pulse from game loop. # * Random times to defeat tick-timing clients and players. # */ pulse_area=0 pulse_mobile=0 pulse_violence=0 pulse_point=0 def update_handler( ): global pulse_area global pulse_mobile global pulse_violence global pulse_point pulse_area -= 1 pulse_mobile -= 1 pulse_violence -= 1 pulse_point -= 1 if pulse_area <= 0: pulse_area = PULSE_AREA area_update ( ) if pulse_mobile <= 0: pulse_mobile = PULSE_MOBILE mobile_update ( ) if pulse_violence <= 0: pulse_violence = PULSE_VIOLENCE fight.violence_update ( ) if pulse_point <= 0: wiznet("TICK!",None,None,WIZ_TICKS,0,0) pulse_point = PULSE_TICK #weather_update ( ) char_update ( ) obj_update ( ) aggr_update( )