Pyom.1.00a/
Pyom.1.00a/pysrc/miniboa/
"""
#**************************************************************************
 *  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( )