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 handler import *
from skills import check_improve
from update import gain_exp
import const

# * Control the fights going on.
# * Called periodically by update_handler.
def violence_update( ):
    for ch in char_list[:]:
        if not ch.fighting or not ch.in_room:
            continue
        victim = ch.fighting
        if IS_AWAKE(ch) and ch.in_room == victim.in_room:
            multi_hit( ch, victim, TYPE_UNDEFINED )
        else:
            stop_fighting( ch, False )
        if not ch.fighting:
            continue
        victim = ch.fighting
        #
        #* Fun for the whole family!
        #*/
        check_assist(ch,victim)
    return

# for auto assisting */
def check_assist( ch, victim):
    for rch in ch.in_room.people[:]:
        if IS_AWAKE(rch) and rch.fighting == None:
            # quick check for ASSIST_PLAYER */
            if not IS_NPC(ch) and IS_NPC(rch) and IS_SET(rch.off_flags,ASSIST_PLAYERS) and rch.level + 6 > victim.level:
                rch.do_emote("screams and attacks!")
                multi_hit(rch,victim,TYPE_UNDEFINED)
                continue
        # PCs next */
        if not IS_NPC(ch) or IS_AFFECTED(ch,AFF_CHARM):
            if( (not IS_NPC(rch) and IS_SET(rch.act,PLR_AUTOASSIST)) \
            or IS_AFFECTED(rch,AFF_CHARM)) \
            and ch.is_same_group(rch) \
            and not is_safe(rch, victim):
                multi_hit (rch,victim,TYPE_UNDEFINED)
                continue
   
        # now check the NPC cases */
        if IS_NPC(ch) and not IS_AFFECTED(ch,AFF_CHARM):
            if (IS_NPC(rch) and IS_SET(rch.off_flags,ASSIST_ALL)) \
            or (IS_NPC(rch) and rch.group and rch.group == ch.group) \
            or (IS_NPC(rch) and rch.race == ch.race and IS_SET(rch.off_flags,ASSIST_RACE)) \
            or (IS_NPC(rch) and IS_SET(rch.off_flags,ASSIST_ALIGN) \
                and ((IS_GOOD(rch) and IS_GOOD(ch)) or (IS_EVIL(rch) and IS_EVIL(ch)) \
                or (IS_NEUTRAL(rch) and IS_NEUTRAL(ch)))) \
            or (rch.pIndexData == ch.pIndexData and IS_SET(rch.off_flags,ASSIST_VNUM)):
                if random.randint(0,1) == 0:
                    continue
        
                target = None
                number = 0
                for vch in ch.in_room.people:
                    if rch.can_see(vch) and vch.is_same_group(victim) and random.randint(0,number) == 0:
                        target = vch
                        number += 1
                if target:
                    rch.do_emote("screams and attacks!")
                    multi_hit(rch,target,TYPE_UNDEFINED)


# * Do one group of attacks.
def multi_hit( ch, victim, dt ):
    # decrement the wait */
    if ch.desc == None:
        ch.wait = max(0,ch.wait - PULSE_VIOLENCE)

    if ch.desc == None:
        ch.daze = max(0,ch.daze - PULSE_VIOLENCE) 


    # no attacks for stunnies -- just a check */
    if ch.position < POS_RESTING:
        return

    if IS_NPC(ch):
        mob_hit(ch,victim,dt)
        return

    one_hit( ch, victim, dt )

    if ch.fighting != victim:
        return

    if IS_AFFECTED(ch,AFF_HASTE):
        one_hit(ch,victim,dt)

    if ch.fighting != victim or dt == 'backstab':
        return

    chance = ch.get_skill('second attack') // 2

    if IS_AFFECTED(ch,AFF_SLOW):
        chance //= 2

    if random.randint(1,99) < chance:
        one_hit( ch, victim, dt )
        check_improve(ch,'second_attack',True,5)
        if ch.fighting != victim:
            return
    
    chance = ch.get_skill('third attack') // 4

    if IS_AFFECTED(ch,AFF_SLOW):
        chance = 0

    if random.randint(1,99) < chance:
        one_hit( ch, victim, dt )
        check_improve(ch,'third attack',True,6)
        if ch.fighting != victim:
            return
    return
# procedure for all mobile attacks */
def mob_hit (ch, victim, dt):
    one_hit(ch,victim,dt)
    if ch.fighting != victim:
        return
    # Area attack -- BALLS nasty! */
    if IS_SET(ch.off_flags,OFF_AREA_ATTACK):
        for vch in ch.in_room.people[:]:
            if vch != victim and vch.fighting == ch:
                one_hit(ch,vch,dt)

    if IS_AFFECTED(ch,AFF_HASTE) or (IS_SET(ch.off_flags,OFF_FAST) and not IS_AFFECTED(ch,AFF_SLOW)):
        one_hit(ch,victim,dt)

    if ch.fighting != victim or dt == 'backstab':
        return

    chance = ch.get_skill("second attack") // 2

    if IS_AFFECTED(ch,AFF_SLOW) and not IS_SET(ch.off_flags,OFF_FAST):
        chance //= 2

    if random.randint(1,99) < chance:
        one_hit(ch,victim,dt)
        if ch.fighting != victim:
            return
    chance = ch.get_skill('third attack') // 4

    if IS_AFFECTED(ch,AFF_SLOW) and not IS_SET(ch.off_flags,OFF_FAST):
        chance = 0

    if random.randint(1,99) < chance:
        one_hit(ch,victim,dt)
        if ch.fighting != victim:
            return

    # oh boy!  Fun stuff! */
    if ch.wait > 0:
        return

    number = random.randint(0,2)

    if number == 1 and IS_SET(ch.act,ACT_MAGE):
        pass
        #  { mob_cast_mage(ch,victim) return } */ 

    if number == 2 and IS_SET(ch.act,ACT_CLERIC):
        pass
        # { mob_cast_cleric(ch,victim) return } */ 

    # now for the skills */

    number = random.randint(0,8)

    if number == 0:
       if IS_SET(ch.off_flags,OFF_BASH):
            ch.do_bash("")
    elif number == 1:
        if IS_SET(ch.off_flags,OFF_BERSERK) and not IS_AFFECTED(ch,AFF_BERSERK):
            ch.do_berserk("")
    elif number == 2:
        if IS_SET(ch.off_flags,OFF_DISARM) \
        or (ch.get_weapon_sn() != 'hand_to_hand' \
        and (IS_SET(ch.act,ACT_WARRIOR) \
        or  IS_SET(ch.act,ACT_THIEF))):
            ch.do_disarm("")
    elif number == 3:
        if IS_SET(ch.off_flags,OFF_KICK):
            ch.do_kick("")
    elif number == 4:
        if IS_SET(ch.off_flags,OFF_KICK_DIRT):
            ch.do_dirt("")
    elif number == 5:
        if IS_SET(ch.off_flags,OFF_TAIL):
            pass  # do_function(ch, &do_tail, "") */ 
    elif number == 6:
        if IS_SET(ch.off_flags,OFF_TRIP):
            ch.do_trip("")
    elif number == 7:
        if IS_SET(ch.off_flags,OFF_CRUSH):
            pass # do_function(ch, &do_crush, "") */ 
    elif number == 8:
        if IS_SET(ch.off_flags,OFF_BACKSTAB):
            ch.do_backstab("")

# * Hit one guy once.
# */
def one_hit( ch, victim, dt ):
    sn = -1
    # just in case */
    if victim == ch or ch == None or victim == None:
        return
    #* Can't beat a dead char!
    #* Guard against weird room-leavings.
    if victim.position == POS_DEAD or ch.in_room != victim.in_room:
        return

     #* Figure out the type of damage message.
    wield = ch.get_eq(WEAR_WIELD)
    if dt == TYPE_UNDEFINED:
        dt = TYPE_HIT
        if wield and wield.item_type == ITEM_WEAPON:
            dt += wield.value[3]
        else :
            dt += ch.dam_type

    if dt < TYPE_HIT:
        if wield:
            dam_type = const.attack_table[wield.value[3]].damage
        else:
            dam_type = const.attack_table[ch.dam_type].damage
    else:
        dam_type = const.attack_table[dt - TYPE_HIT].damage

    if dam_type == -1:
        dam_type = DAM_BASH

    # get the weapon skill */
    sn = ch.get_weapon_sn()
    skill = 20 + ch.get_weapon_skill(sn)

    #* Calculate to-hit-armor-guild-0 versus armor.
    if IS_NPC(ch):
        thac0_00 = 20
        thac0_32 = -4   # as good as a thief */ 
        if IS_SET(ch.act,ACT_WARRIOR):
            thac0_32 = -10
        elif IS_SET(ch.act,ACT_THIEF):
            thac0_32 = -4
        elif IS_SET(ch.act,ACT_CLERIC):
            thac0_32 = 2
        elif IS_SET(ch.act,ACT_MAGE):
            thac0_32 = 6
    else:
        thac0_00 = ch.guild.thac0_00
        thac0_32 = ch.guild.thac0_32
    
    thac0  = interpolate( ch.level, thac0_00, thac0_32 )

    if thac0 < 0:
        thac0 = thac0 // 2

    if thac0 < -5:
        thac0 = -5 + (thac0 + 5) // 2

    thac0 -= GET_HITROLL(ch) * skill // 100
    thac0 += 5 * (100 - skill) // 100

    if dt == 'backstab':
        thac0 -= 10 * (100 - ch.get_skill('backstab'))

    if dam_type == DAM_PIERCE: victim_ac = GET_AC(victim,AC_PIERCE) // 10
    elif dam_type == DAM_BASH: victim_ac = GET_AC(victim,AC_BASH) // 10
    elif dam_type == DAM_SLASH: victim_ac = GET_AC(victim,AC_SLASH) // 10
    else: victim_ac = GET_AC(victim,AC_EXOTIC) // 10
    
    if victim_ac < -15:
        victim_ac = (victim_ac + 15) // 5 - 15
     
    if not ch.can_see(victim):
        victim_ac -= 4

    if victim.position < POS_FIGHTING:
        victim_ac += 4
 
    if victim.position < POS_RESTING:
        victim_ac += 6

     #* The moment of excitement!
    diceroll = random.randint(0,20)
    if diceroll == 0 or ( diceroll != 19 and diceroll < thac0 - victim_ac ):
        # Miss. */
        damage( ch, victim, 0, dt, dam_type, True )
        return
     # Hit.
     # Calc damage.
    if IS_NPC(ch) and (not ch.pIndexData.new_format or wield == None):
        if not ch.pIndexData.new_format:
            dam = random.randint( ch.level // 2, ch.level * 3 // 2 )
            if wield != None:
                dam += dam // 2
        else:
            dam = dice(ch.damage[DICE_NUMBER],ch.damage[DICE_TYPE])
    else:
        if sn != -1:
            check_improve(ch,sn,True,5)
        if wield:
            if wield.pIndexData.new_format:
                dam = dice(wield.value[1],wield.value[2]) * skill // 100
            else:
                dam = random.randint( wield.value[1] * skill // 100, wield.value[2] * skill // 100)

            if ch.get_eq(WEAR_SHIELD) == None:  # no shield = more */
                dam = dam * 11 // 10
            # sharpness! */
            if IS_WEAPON_STAT(wield,WEAPON_SHARP):
                percent = random.randint(1,99)
                if percent <= (skill // 8):
                    dam = 2 * dam + (dam * 2 * percent // 100)
        else:
            low = 1 + 4 * skill // 100
            high = 2 * ch.level // 3 * skill // 100
            if low <= high:
                dam = random.randint(low, high)
            else:
                dam = low
    #
    # * Bonuses.
    if ch.get_skill('enhanced damage') > 0:
        diceroll = random.randint(1,99)
        if diceroll <= ch.get_skill('enhanced_damage'):
            check_improve(ch,'enhanced damage',True,6)
            dam += 2 * ( dam * diceroll // 300)
    if not IS_AWAKE(victim):
        dam *= 2
    elif victim.position < POS_FIGHTING:
        dam = dam * 3 // 2

    if dt == 'backstab' and wield:
        if wield.value[0] != 2:
            dam *= 2 + (ch.level // 10) 
        else:
            dam *= 2 + (ch.level // 8)
    dam += GET_DAMROLL(ch) * min(100,skill) // 100

    if dam <= 0:
        dam = 1

    result = damage( ch, victim, dam, dt, dam_type, True )
    
    # but do we have a funky weapon? */
    if result and wield != None:

        if ch.fighting == victim and IS_WEAPON_STAT(wield,WEAPON_POISON):
            poison = affect_find(wield.affected,'poison')
            if poison:
                level = wield.level
            else:
                level = poison.level
            if not saves_spell(level // 2,victim,DAM_POISON):
                victim.send("You feel poison coursing through your veins.")
                act("$n is poisoned by the venom on $p.", victim,wield,None,TO_ROOM)
                af = AFFECT_DATA()
                af.where     = TO_AFFECTS
                af.type      = 'poison'
                af.level     = level * 3 // 4
                af.duration  = level // 2
                af.location  = APPLY_STR
                af.modifier  = -1
                af.bitvector = AFF_POISON
                victim.affect_join(af)

            # weaken the poison if it's temporary */
            if poison:
                poison.level = max(0,poison.level - 2)
                poison.duration = max(0,poison.duration - 1)
                if poison.level == 0 or poison.duration == 0:
                    act("The poison on $p has worn off.",ch,wield,None,TO_CHAR)

            if ch.fighting == victim and IS_WEAPON_STAT(wield,WEAPON_VAMPIRIC):
                dam = random.randint(1, wield.level // 5 + 1)
                act("$p draws life from $n.",victim,wield,None,TO_ROOM)
                act("You feel $p drawing your life away.", victim,wield,None,TO_CHAR)
                damage(ch,victim,dam,0,DAM_NEGATIVE,False)
                ch.alignment = max(-1000,ch.alignment - 1)
                ch.hit += dam // 2
            if ch.fighting == victim and IS_WEAPON_STAT(wield,WEAPON_FLAMING):
                dam = random.randint(1,wield.level // 4 + 1)
                act("$n is burned by $p.",victim,wield,None,TO_ROOM)
                act("$p sears your flesh.",victim,wield,None,TO_CHAR)
                fire_effect(victim,wield.level // 2,dam,TARGET_CHAR)
                damage(ch,victim,dam,0,DAM_FIRE,False)
            if ch.fighting == victim and IS_WEAPON_STAT(wield,WEAPON_FROST):
                dam = random.randint(1,wield.level // 6 + 2)
                act("$p freezes $n.",victim,wield,None,TO_ROOM)
                act("The cold touch of $p surrounds you with ice.",
                victim,wield,None,TO_CHAR)
                cold_effect(victim,wield.level // 2,dam,TARGET_CHAR)
                damage(ch,victim,dam,0,DAM_COLD,False)
            if ch.fighting == victim and IS_WEAPON_STAT(wield,WEAPON_SHOCKING):
                dam = random.randint(1,wield.level // 5 + 2)
                act("$n is struck by lightning from $p.",victim,wield,None,TO_ROOM)
                act("You are shocked by $p.",victim,wield,None,TO_CHAR)
                shock_effect(victim,wield.level // 2,dam,TARGET_CHAR)
                damage(ch,victim,dam,0,DAM_LIGHTNING,False)
    return

# * Inflict damage from a hit.
def damage(ch,victim,dam,dt,dam_type,show):
    if victim.position == POS_DEAD:
        return False

    #Stop up any residual loopholes.
    if dam > 1200 and dt >= TYPE_HIT:
        print ("BUG: Damage: %d: more than 1200 points!" % dam)
        dam = 1200
        if not IS_IMMORTAL(ch):
            obj = ch.get_eq(WEAR_WIELD)
            ch.send("You really shouldn't cheat.\n\r")
            if obj:
                obj.extract()
    
    # damage reduction */
    if dam > 35:
        dam = (dam - 35) // 2 + 35
    if dam > 80:
        dam = (dam - 80) // 2 + 80 
  
    if victim != ch:
        # Certain attacks are forbidden.
        # Most other attacks are returned.
        if is_safe( ch, victim ):
            return False
        check_killer( ch, victim )

        if victim.position > POS_STUNNED:
            if not victim.fighting:
                set_fighting( victim, ch )
            if victim.timer <= 4:
                victim.position = POS_FIGHTING

        if victim.position > POS_STUNNED:
            if not ch.fighting:
                set_fighting( ch, victim )
        # More charm stuff.
        if victim.master == ch:
            stop_follower( victim )
    # * Inviso attacks ... not.
    if IS_AFFECTED(ch, AFF_INVISIBLE):
        ch.affect_strip("invis")
        ch.affect_strip("mass invis")
        REMOVE_BIT( ch.affected_by, AFF_INVISIBLE )
        act( "$n fades into existence.", ch, None, None, TO_ROOM )

     # Damage modifiers.
    if dam > 1 and not IS_NPC(victim) and victim.pcdata.condition[COND_DRUNK] > 10:
        dam = 9 * dam // 10
    if dam > 1 and IS_AFFECTED(victim, AFF_SANCTUARY):
        dam //= 2

    if dam > 1 and ((IS_AFFECTED(victim, AFF_PROTECT_EVIL) and IS_EVIL(ch)) \
    or (IS_AFFECTED(victim, AFF_PROTECT_GOOD) and IS_GOOD(ch) )):
        dam -= dam // 4

    immune = False
     # Check for parry, and dodge.
    if dt >= TYPE_HIT and ch != victim:
        if check_parry( ch, victim ):
            return False
        if check_dodge( ch, victim ):
            return False
        if check_shield_block(ch,victim):
            return False
    imm = victim.check_immune(dam_type)

    if imm == IS_IMMUNE:
        immune = True
        dam = 0
    elif imm == IS_RESISTANT:
        dam -= dam // 3
    elif imm == IS_VULNERABLE:
        dam += dam // 2
    dam = int(dam)
    if show:
        dam_message( ch, victim, dam, dt, immune )

    if dam == 0:
        return False
     # Hurt the victim.
     # Inform the victim of his new state.
    victim.hit -= dam
    if not IS_NPC(victim) and victim.level >= LEVEL_IMMORTAL and victim.hit < 1:
        victim.hit = 1
    update_pos( victim )

    if victim.position == POS_MORTAL:
        act( "$n is mortally wounded, and will die soon, if not aided.", victim, None, None, TO_ROOM )
        victim.send("You are mortally wounded, and will die soon, if not aided.\n\r")
    elif victim.position == POS_INCAP:
        act( "$n is incapacitated and will slowly die, if not aided.", victim, None, None, TO_ROOM )
        victim.send("You are incapacitated and will slowly die, if not aided.\n\r")
    elif victim.position == POS_STUNNED:
        act( "$n is stunned, but will probably recover.", victim, None, None, TO_ROOM )
        victim.send("You are stunned, but will probably recover.\n\r")
    elif victim.position == POS_DEAD:
        act( "$n is DEAD!!", victim, 0, 0, TO_ROOM )
        victim.send("You have been KILLED!!\n\r\n\r")
    else:
        if dam > victim.max_hit // 4:
            victim.send("That really did HURT!\n\r")
        if victim.hit < victim.max_hit // 4:
            victim.send("You sure are BLEEDING!\n\r")
    # Sleep spells and extremely wounded folks.
    if not IS_AWAKE(victim):
        stop_fighting( victim, False )

    # Payoff for killing things.
    if victim.position == POS_DEAD:
        group_gain( ch, victim )

        if not IS_NPC(victim):
            print ("%s killed by %s at %d" % ( victim.name, ch.short_descr if IS_NPC(ch) else ch.name, ch.in_room.vnum ))
            # Dying penalty:
            # 2/3 way back to previous level.
            if victim.exp > victim.exp_per_level(victim.pcdata.points) * victim.level:
                gain_exp( victim, (2 * (victim.exp_per_level(victim.pcdata.points) * victim.level - victim.exp) // 3) + 50 )

        log_buf = "%s got toasted by %s at %s [room %d]" % ( victim.short_descr if IS_NPC(victim) else victim.name,
            ch.short_descr if IS_NPC(ch) else ch.name, ch.in_room.name, ch.in_room.vnum)
 
        if IS_NPC(victim):
            wiznet(log_buf,None,None,WIZ_MOBDEATHS,0,0)
        else:
            wiznet(log_buf,None,None,WIZ_DEATHS,0,0)

        raw_kill( victim )
        # dump the flags */
        if ch != victim and not IS_NPC(ch) and not ch.is_same_clan(victim):
            if IS_SET(victim.act,PLR_KILLER):
                REMOVE_BIT(victim.act,PLR_KILLER)
            else:
                REMOVE_BIT(victim.act,PLR_THIEF)
            # RT new auto commands */
        corpse = ch.get_obj_list("corpse", ch.in_room.contents)
        if not IS_NPC(ch) and corpse and corpse.item_type == ITEM_CORPSE_NPC and ch.can_see_obj(corpse):
            if IS_SET(ch.act, PLR_AUTOLOOT) and corpse and corpse.contains: # exists and not empty */
                ch.do_get("all corpse")
            
            if IS_SET(ch.act,PLR_AUTOGOLD) and corpse and corpse.contains and not IS_SET(ch.act,PLR_AUTOLOOT):
                coins = ch.get_obj_list("gcash",corpse.contains)
                if coins: ch.do_get("all.gcash corpse")
            
            if IS_SET(ch.act, PLR_AUTOSAC):
                if IS_SET(ch.act,PLR_AUTOLOOT) and corpse and corpse.contains:
                    return True  # leave if corpse has treasure */
                else:
                    ch.do_sacrifice("corpse")
        return True
    
    if victim == ch:
        return True

     #* Take care of link dead people.
    if not IS_NPC(victim) and victim.desc == None:
        if random.randint( 0, victim.wait ) == 0:
            victim.do_recall("")
            return True

    # * Wimp out?
    if IS_NPC(victim) and dam > 0 and victim.wait < PULSE_VIOLENCE // 2:
        if (IS_SET(victim.act, ACT_WIMPY) and random.randint(0,4) == 0 \
        and victim.hit < victim.max_hit // 5) \
        or ( IS_AFFECTED(victim, AFF_CHARM) and victim.master \
        and victim.master.in_room != victim.in_room ):
            victim.do_flee("")

    if not IS_NPC(victim) and victim.hit > 0 and victim.hit <= victim.wimpy and victim.wait < PULSE_VIOLENCE // 2:
        victim.do_flee("")
    return True

def is_safe(ch, victim):
    if victim.in_room == None or ch.in_room == None:
        return True
    if victim.fighting == ch or victim == ch:
        return False
    if IS_IMMORTAL(ch) and ch.level > LEVEL_IMMORTAL:
        return False
    # killing mobiles */
    if IS_NPC(victim):
        # safe room? */
        if IS_SET(victim.in_room.room_flags,ROOM_SAFE):
            ch.send("Not in this room.\n\r")
            return True
        if victim.pIndexData.pShop:
            ch.send("The shopkeeper wouldn't like that.\n\r")
            return True
        # no killing healers, trainers, etc */
        if IS_SET(victim.act,ACT_TRAIN) \
        or IS_SET(victim.act,ACT_PRACTICE) \
        or IS_SET(victim.act,ACT_IS_HEALER) \
        or IS_SET(victim.act,ACT_IS_CHANGER):
            ch.send("I don't think Mota would approve.\n\r")
            return True
        if not IS_NPC(ch):
            # no pets */
            if IS_SET(victim.act,ACT_PET):
                act("But $N looks so cute and cuddly...", ch,None,victim,TO_CHAR)
                return True

            # no charmed creatures unless owner */
            if IS_AFFECTED(victim,AFF_CHARM) and ch != victim.master:
                ch.send("You don't own that monster.\n\r")
                return True
    # killing players */
    else:
        # NPC doing the killing */
        if IS_NPC(ch):
            # safe room check */
            if IS_SET(victim.in_room.room_flags,ROOM_SAFE):
                ch.send("Not in this room.\n\r")
                return True

            # charmed mobs and pets cannot attack players while owned */
            if IS_AFFECTED(ch,AFF_CHARM) and ch.master and  ch.master.fighting != victim:
                ch.send("Players are your friends!\n\r")
                return True
        # player doing the killing */
        else:
            if not ch.is_clan():
                ch.send("Join a clan if you want to kill players.\n\r")
                return True

            if IS_SET(victim.act,PLR_KILLER) or IS_SET(victim.act,PLR_THIEF):
                return False

            if not victim.is_clan():
                ch.send("They aren't in a clan, leave them alone.\n\r")
                return True

            if ch.level > victim.level + 8:
                ch.send("Pick on someone your own size.\n\r")
                return True
    return False
 
def is_safe_spell(ch, victim, area ):
    if victim.in_room == None or ch.in_room == None:
        return True
    if victim == ch and area:
        return True
    if victim.fighting == ch or victim == ch:
        return False
    if IS_IMMORTAL(ch) and ch.level > LEVEL_IMMORTAL and not area:
        return False
    # killing mobiles */
    if IS_NPC(victim):
        # safe room? */
        if IS_SET(victim.in_room.room_flags,ROOM_SAFE):
            return True
        if victim.pIndexData.pShop:
            return True
        # no killing healers, trainers, etc */
        if IS_SET(victim.act,ACT_TRAIN) \
        or IS_SET(victim.act,ACT_PRACTICE) \
        or IS_SET(victim.act,ACT_IS_HEALER) \
        or IS_SET(victim.act,ACT_IS_CHANGER):
            return True
        if not IS_NPC(ch):
            # no pets */
            if IS_SET(victim.act,ACT_PET):
                return True
            # no charmed creatures unless owner */
            if IS_AFFECTED(victim,AFF_CHARM) and (area or ch != victim.master):
                return True
            # legal kill? -- cannot hit mob fighting non-group member */
            if victim.fighting != None and not ch.is_same_group(victim.fighting):
                return True
        else:
            # area effect spells do not hit other mobs */
            if area and not victim.is_same_group(ch.fighting):
                return True
    # killing players */
    else:
        if area and IS_IMMORTAL(victim) and victim.level > LEVEL_IMMORTAL:
            return True

        # NPC doing the killing */
        if IS_NPC(ch):
            # charmed mobs and pets cannot attack players while owned */
            if IS_AFFECTED(ch,AFF_CHARM) and ch.master and ch.master.fighting != victim:
                return True
            # safe room? */
            if IS_SET(victim.in_room.room_flags,ROOM_SAFE):
                return True

            # legal kill? -- mobs only hit players grouped with opponent*/
            if ch.fighting and not ch.fighting.is_same_group(victim):
                return True
        # player doing the killing */
        else:
            if not ch.is_clan():
                return True
            if IS_SET(victim.act,PLR_KILLER) or IS_SET(victim.act,PLR_THIEF):
                return False
            if not victim.is_clan():
                return True
            if ch.level > victim.level + 8:
                return True
    return False
#
# * See if an attack justifies a KILLER flag.
def check_killer( ch, victim ):
#     * Follow charm thread to responsible character.
#     * Attacking someone's charmed char is hostile!
    while IS_AFFECTED(victim, AFF_CHARM) and victim.master != None:
        victim = victim.master

     # NPC's are fair game.
     # So are killers and thieves.
    if IS_NPC(victim) or IS_SET(victim.act, PLR_KILLER) or IS_SET(victim.act, PLR_THIEF):
        return

     # Charm-o-rama.
    if IS_SET(ch.affected_by, AFF_CHARM):
        if ch.master == None:
            print ("BUG: Check_killer: %s bad AFF_CHARM" % (ch.short_descr if IS_NPC(ch) else ch.name ))
            ch.affect_strip('charm person')
            REMOVE_BIT(ch.affected_by, AFF_CHARM)
            return
    #    send_to_char( "*** You are now a KILLER!! ***\n\r", ch.master )
    #    SET_BIT(ch.master.act, PLR_KILLER)
        stop_follower( ch )
        return

     # NPC's are cool of course (as long as not charmed).
     # Hitting yourself is cool too (bleeding).
     # So is being immortal (Alander's idea).
     # And current killers stay as they are.
    if IS_NPC(ch) or ch == victim or ch.level >= LEVEL_IMMORTAL \
    or not ch.is_clan() or IS_SET(ch.act, PLR_KILLER) or ch.fighting == victim:
        return

    ch.send("*** You are now a KILLER!! ***\n\r")
    SET_BIT(ch.act, PLR_KILLER)
    wiznet("$N is attempting to murder %s" % victim.name,ch,None,WIZ_FLAGS,0,0)
    save_char_obj( ch )
    return
# Check for parry.
def check_parry( ch, victim ):
    if IS_AWAKE(victim):
        return False
    chance = victim.get_skill('parry') // 2

    if victim.get_eq(WEAR_WIELD) == None:
        if IS_NPC(victim):
            chance //= 2
        else:
            return False
    if not ch.can_see(victim):
        chance //= 2

    if random.randint(1,99) >= chance + victim.level - ch.level:
        return False

    act( "You parry $n's attack.",  ch, None, victim, TO_VICT    )
    act( "$N parries your attack.", ch, None, victim, TO_CHAR    )
    check_improve(victim,'parry',True,6)
    return True

# Check for shield block.
def check_shield_block( ch, victim ):
    if not IS_AWAKE(victim):
        return False
    chance = victim.get_skill('shield block') // 5 + 3
    if victim.get_eq(WEAR_SHIELD) == None:
        return False
    if random.randint(1,99) >= chance + victim.level - ch.level:
        return False
    act( "You block $n's attack with your shield.",  ch, None, victim, TO_VICT)
    act( "$N blocks your attack with a shield.", ch, None, victim, TO_CHAR)
    check_improve(victim,'shield block',True,6)
    return True

# Check for dodge.
def check_dodge( ch, victim ):
    if not IS_AWAKE(victim):
        return False
    chance = victim.get_skill('dodge') // 2
    if not victim.can_see(ch):
        chance //= 2
    if random.randint(1,99) >= chance + victim.level - ch.level:
        return False
    act( "You dodge $n's attack.", ch, None, victim, TO_VICT    )
    act( "$N dodges your attack.", ch, None, victim, TO_CHAR    )
    check_improve(victim,'dodge',True,6)
    return True

# Set position of a victim.
def update_pos(victim):
    if victim.hit > 0:
        if victim.position <= POS_STUNNED:
            victim.position = POS_STANDING
        return
    if IS_NPC(victim) and victim.hit < 1:
        victim.position = POS_DEAD
        return
    if victim.hit <= -11:
        victim.position = POS_DEAD
        return

    if victim.hit <= -6: victim.position = POS_MORTAL
    elif victim.hit <= -3: victim.position = POS_INCAP
    else: victim.position = POS_STUNNED

# Start fights.
def set_fighting( ch, victim ):
    if ch.fighting != None:
        print ("BUG: Set_fighting: already fighting")
        return

    if IS_AFFECTED(ch, AFF_SLEEP):
        ch.affect_strip('sleep')

    ch.fighting = victim
    ch.position = POS_FIGHTING

# Stop fights.
def stop_fighting( ch, fBoth ):
    for fch in char_list:
        if fch == ch or ( fBoth and fch.fighting == ch ):
            fch.fighting = None
            fch.position = fch.default_pos if IS_NPC(fch) else POS_STANDING
            update_pos( fch )
    return
#
# * Make a corpse out of a character.
def make_corpse(ch):
    from db import create_object
    if IS_NPC(ch):
        name = ch.short_descr
        corpse      = create_object(obj_index_hash[OBJ_VNUM_CORPSE_NPC], 0)
        corpse.timer   = random.randint( 3, 6 )
        if ch.gold > 0:
            create_money(ch.gold, ch.silver).to_obj(corpse)
            ch.gold = 0
            ch.silver = 0
        corpse.cost = 0
    else:
        name = ch.name
        corpse = create_object(obj_index_hash[OBJ_VNUM_CORPSE_PC], 0)
        corpse.timer = random.randint(25, 40)
        REMOVE_BIT(ch.act, PLR_CANLOOT)
        if not ch.is_clan():
            corpse.owner = ch.name
        else:
            corpse.owner = ""
            if ch.gold > 1 or ch.silver > 1:
                create_money(ch.gold // 2, ch.silver // 2).to_obj(corpse)
                ch.gold -= ch.gold // 2
                ch.silver -= ch.silver // 2
        corpse.cost = 0
    corpse.level = ch.level
    corpse.short_descr = corpse.short_descr % name
    corpse.description = corpse.description % name

    for obj in ch.carrying[:]:
        floating = False
        if obj.wear_loc == WEAR_FLOAT:
            floating = True
        obj.from_char()
        if obj.item_type == ITEM_POTION:
            obj.timer = random.randint(500,1000)
        if obj.item_type == ITEM_SCROLL:
            obj.timer = random.randint(1000,2500)
        if IS_SET(obj.extra_flags,ITEM_ROT_DEATH) and not floating:
            obj.timer = random.randint(5,10)
            REMOVE_BIT(obj.extra_flags,ITEM_ROT_DEATH)
        REMOVE_BIT(obj.extra_flags,ITEM_VIS_DEATH)

        if IS_SET( obj.extra_flags, ITEM_INVENTORY ):
            obj.extract()
        elif floating:
            if IS_OBJ_STAT(obj,ITEM_ROT_DEATH): # get rid of it! */
                if obj.contains:
                    act("$p evaporates,scattering its contents.", ch,obj,None,TO_ROOM)
                    for o in obj.contains[:]:
                        o.from_obj()
                        o.to_room(ch.in_room)
                else:
                    act("$p evaporates.", ch,obj,None,TO_ROOM)
                obj.extract()
            else:
                act("$p falls to the floor.",ch,obj,None,TO_ROOM)
                obj.to_room(ch.in_room)
        else:
            obj.to_obj(corpse)
    corpse.to_room(ch.in_room)
    return

#
# Improved Death_cry contributed by Diavolo.
def death_cry( ch ):
    from db import create_object
    vnum = 0
    msg = "You hear $n's death cry."
    num = random.randint(0,7)
    if num == 0: msg  = "$n hits the ground ... DEAD."
    elif num == 1: 
        if ch.material == 0:
            msg  = "$n splatters blood on your armor."     
    elif num == 2:                            
        if IS_SET(ch.parts,PART_GUTS):
            msg = "$n spills $s guts all over the floor."
            vnum = OBJ_VNUM_GUTS
    elif num ==  3: 
        if IS_SET(ch.parts,PART_HEAD):
            msg  = "$n's severed head plops on the ground."
            vnum = OBJ_VNUM_SEVERED_HEAD               
    elif num ==  4: 
        if IS_SET(ch.parts,PART_HEART):
            msg  = "$n's heart is torn from $s chest."
            vnum = OBJ_VNUM_TORN_HEART             
    elif num ==  5: 
        if IS_SET(ch.parts,PART_ARMS):
            msg  = "$n's arm is sliced from $s dead body."
            vnum = OBJ_VNUM_SLICED_ARM             
    elif num ==  6: 
        if IS_SET(ch.parts,PART_LEGS):
            msg  = "$n's leg is sliced from $s dead body."
            vnum = OBJ_VNUM_SLICED_LEG             
    elif num == 7:
        if IS_SET(ch.parts,PART_BRAINS):
            msg = "$n's head is shattered, and $s brains splash all over you."
            vnum = OBJ_VNUM_BRAINS
    act( msg, ch, None, None, TO_ROOM )
    if vnum != 0:
        name = ch.short_descr if IS_NPC(ch) else ch.name
        obj = create_object( obj_index_hash[vnum], 0 )
        obj.timer = random.randint( 4, 7 )

        obj.short_descr = obj.short_descr % name
        obj.description = obj.description % name
        if obj.item_type == ITEM_FOOD:
            if IS_SET(ch.form,FORM_POISON):
                obj.value[3] = 1
            elif not IS_SET(ch.form,FORM_EDIBLE):
                obj.item_type = ITEM_TRASH
            obj.to_room(ch.in_room)

    if IS_NPC(ch):
        msg = "You hear something's death cry."
    else:
        msg = "You hear someone's death cry."

    was_in_room = ch.in_room
    for pexit in was_in_room.exit:
        if pexit and pexit.to_room and pexit.to_room != was_in_room:
            ch.in_room = pexit.to_room
            act( msg, ch, None, None, TO_ROOM )
    ch.in_room = was_in_room
    return

def raw_kill( victim ):
    stop_fighting( victim, True )
    death_cry( victim )
    make_corpse( victim )

    if IS_NPC(victim):
        victim.pIndexData.killed += 1
        #kill_table[max(0, min(victim.level, MAX_LEVEL-1))].killed += 1
        victim.extract(True)
        return

    victim.extract(False)
    for af in victim.affected[:]:
        victim.affect_remove(af)
    victim.affected_by = victim.race.aff
    victim.armor = [100 for i in range(4)]
    victim.position = POS_RESTING
    victim.hit = max( 1, victim.hit  )
    victim.mana = max( 1, victim.mana )
    victim.move = max( 1, victim.move )
#  save_char_obj( victim ) we're stable enough to not need this :) */
    return

def group_gain( ch, victim ):
    # Monsters don't get kill xp's or alignment changes.
    # P-killing doesn't help either.
    # Dying of mortal wounds or poison doesn't give xp to anyone!
    if victim == ch:
        return
    members = 0
    group_levels = 0
    for gch in ch.in_room.people:
        if gch.is_same_group(ch):
            members += 1
            group_levels += gch.level // 2 if IS_NPC(gch) else gch.level

    if members == 0:
        print ("BUG: Group_gain: members. %s" % members)
        members = 1
        group_levels = ch.level 

    lch = ch.leader if ch.leader else ch

    for gch in ch.in_room.people:
        if not gch.is_same_group(ch) or IS_NPC(gch):
            continue

        #Taken out, add it back if you want it
        if gch.level - lch.level >= 5:
            gch.send("You are too high for this group.\n\r")
            continue
        if gch.level - lch.level <= -5:
            gch.send("You are too low for this group.\n\r")
            continue
        #*/

        xp = xp_compute( gch, victim, group_levels )  
        gch.send("You receive %d experience points.\n\r" % xp)
        gain_exp( gch, xp )
        for obj in ch.carrying[:]:
            if obj.wear_loc == WEAR_NONE:
                continue
            if (IS_OBJ_STAT(obj, ITEM_ANTI_EVIL) and IS_EVIL(ch) ) \
            or (IS_OBJ_STAT(obj, ITEM_ANTI_GOOD) and IS_GOOD(ch) ) \
            or (IS_OBJ_STAT(obj, ITEM_ANTI_NEUTRAL) and IS_NEUTRAL(ch) ):
                act( "You are zapped by $p.", ch, obj, None, TO_CHAR )
                act( "$n is zapped by $p.",   ch, obj, None, TO_ROOM )
                obj.from_char()
                obj.to_room(ch.in_room)

 # Compute xp for a kill.
 # Also adjust alignment of killer.
 # Edit this function to change xp computations.
def xp_compute( gch, victim, total_levels ):
    level_range = victim.level - gch.level
    # compute the base exp */
    if level_range == -9 : base_exp = 1
    elif level_range == -8: base_exp = 2
    elif level_range == -7: base_exp = 5
    elif level_range == -6: base_exp = 9
    elif level_range == -5: base_exp = 11
    elif level_range == -4: base_exp = 22
    elif level_range == -3: base_exp = 33
    elif level_range == -2: base_exp = 50
    elif level_range == -1: base_exp = 66
    elif level_range == 0: base_exp = 83
    elif level_range == 1: base_exp = 99
    elif level_range == 2: base_exp = 121
    elif level_range == 3: base_exp = 143
    elif level_range == 4: base_exp = 165
    else: base_exp = 0
    
    if level_range > 4:
        base_exp = 160 + 20 * (level_range - 4)
    # do alignment computations */
    align = victim.alignment - gch.alignment
    if IS_SET(victim.act,ACT_NOALIGN):
        pass    # no change */
    elif align > 500: # monster is more good than slayer */
        change = (align - 500) * base_exp // 500 * gch.level // total_levels 
        change = max(1,change)
        gch.alignment = max(-1000,gch.alignment - change)
    elif align < -500: # monster is more evil than slayer */
        change =  ( -1 * align - 500) * base_exp // 500 * gch.level // total_levels
        change = max(1,change)
        gch.alignment = min(1000,gch.alignment + change)
    else: # improve this someday */
        change =  gch.alignment * base_exp // 500 * gch.level // total_levels  
        gch.alignment -= change
    # calculate exp multiplier */
    if IS_SET(victim.act,ACT_NOALIGN):
        xp = base_exp
    elif gch.alignment > 500:  # for goodie two shoes */
        if victim.alignment < -750:
            xp = (base_exp *4) // 3
        elif victim.alignment < -500:
            xp = (base_exp * 5) // 4
        elif victim.alignment > 750:
            xp = base_exp // 4
        elif victim.alignment > 500:
            xp = base_exp // 2
        elif victim.alignment > 250:
            xp = (base_exp * 3) // 4 
        else:
            xp = base_exp
    elif gch.alignment < -500:# for baddies */
        if victim.alignment > 750:
            xp = (base_exp * 5) // 4
        elif victim.alignment > 500:
            xp = (base_exp * 11) // 10 
        elif victim.alignment < -750:
            xp = base_exp // 2
        elif victim.alignment < -500:
            xp = (base_exp * 3) // 4
        elif victim.alignment < -250:
            xp = (base_exp * 9) // 10
        else:
            xp = base_exp
    elif gch.alignment > 200:  # a little good */
        if victim.alignment < -500:
            xp = (base_exp * 6) // 5
        elif victim.alignment > 750:
            xp = base_exp // 2
        elif victim.alignment > 0:
            xp = (base_exp * 3) // 4 
        else:
            xp = base_exp
    elif gch.alignment < -200: # a little bad */
        if victim.alignment > 500:
            xp = (base_exp * 6) // 5
        elif victim.alignment < -750:
            xp = base_exp // 2
        elif victim.alignment < 0:
            xp = (base_exp * 3) // 4
        else:
            xp = base_exp
    else: # neutral */
        if victim.alignment > 500 or victim.alignment < -500:
            xp = (base_exp * 4) // 3
        elif victim.alignment < 200 and victim.alignment > -200:
            xp = base_exp // 2
        else:
            xp = base_exp
    # more exp at the low levels */
    if gch.level < 6:
        xp = 10 * xp // (gch.level + 4)

    # less at high */
    if gch.level > 35:
        xp =  15 * xp // (gch.level - 25 )
    # reduce for playing time */
    # compute quarter-hours per level */
    time_per_level = 4 * (gch.played + (int) (current_time - gch.logon)) // 3600 // gch.level
    time_per_level = max(2, min(time_per_level,12))
    if gch.level < 15:  # make it a curve */
        time_per_level = max(time_per_level,(15 - gch.level))
    xp = xp * time_per_level // 12
    # randomize the rewards */
    xp = random.randint (int(xp * 3 // 4), int(xp * 5 // 4))
    # adjust for grouping */
    xp = xp * gch.level // ( max(1,total_levels -1) )
    return xp

def dam_message( ch, victim, dam, dt, immune ):
    if ch == None or victim == None:
        return

    if dam == 0: msg = {'vs':"miss", 'vp':"misses"}
    elif dam <= 4: msg = {'vs':"scratch", 'vp':"scratches"}
    elif dam <=   8: msg = {'vs':"graze", 'vp':"grazes"}
    elif dam <=  12: msg = {'vs':"hit", 'vp':"hits"}
    elif dam <=  16: msg = {'vs':"injure", 'vp':"injures"}
    elif dam <=  20: msg = {'vs':"wound", 'vp':"wounds"}
    elif dam <=  24: msg = {'vs':"maul", 'vp':"mauls"}
    elif dam <=  28: msg = {'vs':"decimate", 'vp':"decimates"}
    elif dam <=  32: msg = {'vs':"devastate", 'vp':"devastates"}
    elif dam <=  36: msg = {'vs':"maim", 'vp':"maims"}
    elif dam <=  40: msg = {'vs':"MUTILATE", 'vp':"MUTILATES"}
    elif dam <=  44: msg = {'vs':"DISEMBOWEL", 'vp':"DISEMBOWELS"}
    elif dam <=  48: msg = {'vs':"DISMEMBER", 'vp':"DISMEMBERS"}
    elif dam <=  52: msg = {'vs':"MASSACRE", 'vp':"MASSACRES"}
    elif dam <=  56: msg = {'vs':"MANGLE", 'vp':"MANGLES"}
    elif dam <=  60: msg = {'vs':"*** DEMOLISH ***", 'vp':"*** DEMOLISHES ***"}
    elif dam <=  75: msg = {'vs':"*** DEVASTATE ***", 'vp':"*** DEVASTATES ***"}
    elif dam <= 100: msg = {'vs':"=== OBLITERATE ===", 'vp':"=== OBLITERATES ==="}
    elif dam <= 125: msg = {'vs':">>> ANNIHILATE <<<", 'vp':">>> ANNIHILATES <<<"}
    elif dam <= 150: msg = {'vs':"<<< ERADICATE >>>", 'vp':"<<< ERADICATES >>>"}
    else: msg = {'vs':"do UNSPEAKABLE things to", 'vp':"does UNSPEAKABLE things to"}
    vs = msg['vs']
    vp = msg['vp']
    punct = '.' if dam <= 24 else '!'
    if dt == TYPE_HIT:
        if ch == victim:
            buf1 = "$n %s $melf%c" % (vp,punct)
            buf2 = "You %s yourself%c" % (vs, punct)
        else:
            buf1 = "$n %s $N%c" % ( vp, punct )
            buf2 = "You %s $N%c" % ( vs, punct )
            buf3 = "$n %s you%c" % ( vp, punct )
    else:
        if dt >= 0 and dt < MAX_SKILL:
            attack  = const.skill_table[dt].noun_damage
        elif dt >= TYPE_HIT and dt < TYPE_HIT + len(const.attack_table):
            attack = const.attack_table[dt - TYPE_HIT].noun
        else:
            print ("BUG: Dam_message: bad dt %d.")
            dt = TYPE_HIT
            attack  = const.attack_table[0].name
        if immune:
            if ch == victim:
                buf1 = "$n is unaffected by $s own %s." % attack
                buf2 = "Luckily, you are immune to that."
            else:
                buf1 = "$N is unaffected by $n's %s!" % attack
                buf2 = "$N is unaffected by your %s!" % attack
                buf3 = "$n's %s is powerless against you." % attack
        else:
            if ch == victim:
                buf1 = "$n's %s %s $m%c" % (attack,vp,punct)
                buf2 = "Your %s %s you%c" % (attack,vp,punct)
            else:
                buf1 = "$n's %s %s $N%c" % (attack, vp, punct)
                buf2 = "Your %s %s $N%c" %  (attack, vp, punct)
                buf3 = "$n's %s %s you%c" % (attack, vp, punct)

    if ch == victim:
        act(buf1,ch,None,None,TO_ROOM)
        act(buf2,ch,None,None,TO_CHAR)
    else:
        act( buf1, ch, None, victim, TO_NOTVICT )
        act( buf2, ch, None, victim, TO_CHAR )
        act( buf3, ch, None, victim, TO_VICT )
    return

# * Disarm a creature.
# * Caller must check for successful attack.
def disarm( ch, victim ):
    obj = victim.get_eq(WEAR_WIELD)
    if not obj:
        ch.send("I think you're taking disarm a little too literally")
        return

    if IS_OBJ_STAT(obj,ITEM_NOREMOVE):
        act("$S weapon won't budge!",ch,None,victim,TO_CHAR)
        act("$n tries to disarm you, but your weapon won't budge!", ch,None,victim,TO_VICT)
        act("$n tries to disarm $N, but fails.",ch,None,victim,TO_NOTVICT)
        return
    act( "$n DISARMS you and sends your weapon flying!", ch, None, victim, TO_VICT)
    act( "You disarm $N!",  ch, None, victim, TO_CHAR    )
    act( "$n disarms $N!",  ch, None, victim, TO_NOTVICT )
    obj.from_char()
    if IS_OBJ_STAT(obj,ITEM_NODROP) or IS_OBJ_STAT(obj,ITEM_INVENTORY):
        obj.to_char(victim)
    else:
        obj.to_room(victim.in_room)
        if IS_NPC(victim) and victim.wait == 0 and victim.can_see_obj(obj):
            get_obj(victim,obj,None)
    return

def do_berserk( self, argument):
    ch = self
    chance = ch.get_skill('berserk')
    if chance== 0 or (IS_NPC(ch) and not IS_SET(ch.off_flags,OFF_BERSERK)) \
    or  (not IS_NPC(ch) and ch.level < const.skill_table['berserk'].skill_level[ch.guild.name]):
        ch.send("You turn red in the face, but nothing happens.\n\r")
        return

    if IS_AFFECTED(ch,AFF_BERSERK) or is_affected(ch,'berserk') or is_affected(ch,"frenzy"):
        ch.send("You get a little madder.\n\r")
        return
    if IS_AFFECTED(ch,AFF_CALM):
        ch.send("You're feeling to mellow to berserk.\n\r")
        return
    if ch.mana < 50:
        ch.send("You can't get up enough energy.\n\r")
        return
    # modifiers */
    # fighting */
    if ch.position == POS_FIGHTING:
        chance += 10

    # damage -- below 50% of hp helps, above hurts */
    hp_percent = 100 * ch.hit // ch.max_hit
    chance += 25 - hp_percent // 2

    if random.randint(1,99) < chance:
        WAIT_STATE(ch,PULSE_VIOLENCE)
        ch.mana -= 50
        ch.move //= 2
        # heal a little damage */
        ch.hit += ch.level * 2
        ch.hit = min(ch.hit,ch.max_hit)
        ch.send("Your pulse races as you are consumed by rage!\n\r")
        act("$n gets a wild look in $s eyes.",ch,None,None,TO_ROOM)
        check_improve(ch,'berserk',True,2)
        af = AFFECT_DATA()
        af.where    = TO_AFFECTS
        af.type     = 'berserk'
        af.level    = ch.level
        af.duration = number_fuzzy(ch.level // 8)
        af.modifier = max(1,ch.level // 5)
        af.bitvector    = AFF_BERSERK

        af.location = APPLY_HITROLL
        ch.affect_add(af)

        af.location = APPLY_DAMROLL
        ch.affect_add(af)

        af.modifier = max(10,10 * (ch.level // 5))
        af.location = APPLY_AC
        ch.affect_add(af)
    else:
        WAIT_STATE(ch,3 * PULSE_VIOLENCE)
        ch.mana -= 25
        ch.move //= 2

        ch.send("Your pulse speeds up, but nothing happens.\n\r")
        check_improve(ch,'berserk',False,2)

def do_bash( ch, argument ):
    arghold, arg = read_word(argument)
    chance = ch.get_skill('bash')
    if chance == 0 or (IS_NPC(ch) and not IS_SET(ch.off_flags,OFF_BASH)) \
    or (not IS_NPC(ch) and ch.level < const.skill_table['bash'].skill_level[ch.guild.name] ):
        ch.send("Bashing? What's that?\n\r")
        return
    victim = None 
    if not arg:
        victim = ch.fighting
        if not victim:
            ch.send("But you aren't fighting anyone!\n\r")
            return
    else:
        victim = ch.get_char_room(arg)
        if not victim:
            ch.send("They aren't here.\n\r")
            return
    if victim.position < POS_FIGHTING:
        act("You'll have to let $M get back up first.",ch,None,victim,TO_CHAR)
        return
    if victim == ch:
        ch.send("You try to bash your brains out, but fail.\n\r")
        return
    if is_safe(ch,victim):
        return
    if IS_NPC(victim) and victim.fighting and not ch.is_same_group(victim.fighting):
        ch.send("Kill stealing is not permitted.\n\r")
        return
    if IS_AFFECTED(ch,AFF_CHARM) and ch.master == victim:
        act("But $N is your friend!",ch,None,victim,TO_CHAR)
        return

    # modifiers */
    # size  and weight */
    chance += ch.carry_weight // 250
    chance -= victim.carry_weight // 200
    if ch.size < victim.size:
        chance += (ch.size - victim.size) * 15
    else:
        chance += (ch.size - victim.size) * 10 


    # stats */
    chance += ch.get_curr_stat(STAT_STR)
    chance -= (victim.get_curr_stat(STAT_DEX) * 4) // 3
    chance -= GET_AC(victim,AC_BASH) // 25
    # speed */
    if IS_SET(ch.off_flags,OFF_FAST) or IS_AFFECTED(ch,AFF_HASTE):
        chance += 10
    if IS_SET(victim.off_flags,OFF_FAST) or IS_AFFECTED(victim,AFF_HASTE):
        chance -= 30
    # level */
    chance += (ch.level - victim.level)
    if not IS_NPC(victim) and chance < victim.get_skill('dodge'):
        pass
        #act("$n tries to bash you, but you dodge it.",ch,None,victim,TO_VICT)
        #act("$N dodges your bash, you fall flat on your face.",ch,None,victim,TO_CHAR)
        #WAIT_STATE(ch,const.skill_table['bash'].beats)
        #return*/
        chance -= 3 * (victim.get_skill('dodge') - chance)
    # now the attack */
    if random.randint(1,99) < chance:
        act("$n sends you sprawling with a powerful bash!", ch,None,victim,TO_VICT)
        act("You slam into $N, and send $M flying!",ch,None,victim,TO_CHAR)
        act("$n sends $N sprawling with a powerful bash.", ch,None,victim,TO_NOTVICT)
        check_improve(ch,'bash',True,1)
        DAZE_STATE(victim, 3 * PULSE_VIOLENCE)
        WAIT_STATE(ch,const.skill_table['bash'].beats)
        victim.position = POS_RESTING
        damage(ch,victim,random.randint(2,2 + 2 * ch.size + chance // 20),'bash', DAM_BASH,False)
    else:
        damage(ch,victim,0,'bash',DAM_BASH,False)
        act("You fall flat on your face!", ch,None,victim,TO_CHAR)
        act("$n falls flat on $s face.", ch,None,victim,TO_NOTVICT)
        act("You evade $n's bash, causing $m to fall flat on $s face.", ch,None,victim,TO_VICT)
        check_improve(ch,'bash',False,1)
        ch.position = POS_RESTING
        WAIT_STATE(ch,const.skill_table['bash'].beats * 3 // 2) 
    check_killer(ch,victim)

def do_dirt( self, argument ):
    ch = self
    arghold, arg = read_word(argument)
    chance = ch.get_skill('dirt kicking')
    if chance == 0 or (IS_NPC(ch) and not IS_SET(ch.off_flags,OFF_KICK_DIRT)) \
    or ( not IS_NPC(ch) and ch.level < const.skill_table['dirt kicking'].skill_level[ch.guild.name]):
        ch.send("You get your feet dirty.\n\r")
        return
    if not arg:
        victim = ch.fighting
        if victim == None:
            ch.send("But you aren't in combat!\n\r")
            return
    else:
        victim = ch.get_char_room(arg)
        if victim == None:
            ch.send("They aren't here.\n\r")
            return
    if IS_AFFECTED(victim,AFF_BLIND):
        act("$E's already been blinded.",ch,None,victim,TO_CHAR)
        return
    if victim == ch:
        ch.send("Very funny.\n\r")
        return
    if is_safe(ch,victim):
        return
    if IS_NPC(victim) and victim.fighting != None and not ch.is_same_group(victim.fighting):
        ch.send("Kill stealing is not permitted.\n\r")
        return
    if IS_AFFECTED(ch,AFF_CHARM) and ch.master == victim:
        act("But $N is such a good friend!",ch,None,victim,TO_CHAR)
        return

    # modifiers */
    # dexterity */
    chance += ch.get_curr_stat(STAT_DEX)
    chance -= 2 * victim.get_curr_stat(STAT_DEX)

    # speed  */
    if IS_SET(ch.off_flags,OFF_FAST) or IS_AFFECTED(ch,AFF_HASTE):
        chance += 10
    if IS_SET(victim.off_flags,OFF_FAST) or IS_AFFECTED(victim,AFF_HASTE):
        chance -= 25
    # level */
    chance += (ch.level - victim.level) * 2

    # sloppy hack to prevent false zeroes */
    if chance % 5 == 0:
        chance += 1
    # terrain */
    nochance = [ SECT_WATER_SWIM, SECT_WATER_NOSWIM, SECT_AIR ]
    modifiers = { SECT_INSIDE: -20,
                  SECT_CITY: -10,
                  SECT_FIELD: 5,
                  SECT_MOUNTAIN: -10,
                  SECT_DESERT: 10
                }
    if ch.in_room.sector_type in nochance:
        chance = 0
    elif ch.in_room.sector_type in modifiers:
        chance += modifiers[ch.in_room.sector_type]

    if chance == 0:
        ch.send("There isn't any dirt to kick.\n\r")
        return
    # now the attack */
    if random.randint(1,99) < chance:
        act("$n is blinded by the dirt in $s eyes!",victim,None,None,TO_ROOM)
        act("$n kicks dirt in your eyes!",ch,None,victim,TO_VICT)
        damage(ch,victim,random.randint(2,5),'dirt kicking',DAM_NONE,False)
        victim.send("You can't see a thing!\n\r")
        check_improve(ch,'dirt kicking',True,2)
        WAIT_STATE(ch,const.skill_table['dirt kicking'].beats)
        af = AFFECT_DATA()
        af.where    = TO_AFFECTS
        af.type     = 'dirt kicking'
        af.level    = ch.level
        af.duration = 0
        af.location = APPLY_HITROLL
        af.modifier = -4
        af.bitvector    = AFF_BLIND
        victim.affect_add(af)
    else:
        damage(ch,victim,0,'dirt kicking',DAM_NONE,True)
        check_improve(ch,'dirt kicking',False,2)
        WAIT_STATE(ch,const.skill_table['dirt kicking'].beats)
    check_killer(ch,victim)

def do_trip( self, argument ):
    ch = self
    arghold, arg = read_word(argument)
    chance = ch.get_skill('trip')
    if chance == 0 or (IS_NPC(ch) and not IS_SET(ch.off_flags,OFF_TRIP)) \
    or ( not IS_NPC(ch) and ch.level < const.skill_table['trip'].skill_level[ch.guild.name]):
        ch.send("Tripping?  What's that?\n\r")
        return
    if not arg:
        victim = ch.fighting
        if victim == None:
            ch.send("But you aren't fighting anyone!\n\r")
            return
    else:
        victim = ch.get_char_room(arg)
        if victim == None:
            ch.send("They aren't here.\n\r")
            return
    if is_safe(ch,victim):
        return
    if IS_NPC(victim) and victim.fighting and not ch.is_same_group(victim.fighting):
        ch.send("Kill stealing is not permitted.\n\r")
        return
    if IS_AFFECTED(victim,AFF_FLYING):
        act("$S feet aren't on the ground.",ch,None,victim,TO_CHAR)
        return
    if victim.position < POS_FIGHTING:
        act("$N is already down.",ch,None,victim,TO_CHAR)
        return
    if victim == ch:
        ch.send("You fall flat on your face!\n\r")
        WAIT_STATE(ch,2 * const.skill_table['trip'].beats)
        act("$n trips over $s own feet!",ch,None,None,TO_ROOM)
        return

    if IS_AFFECTED(ch,AFF_CHARM) and ch.master == victim:
        act("$N is your beloved master.",ch,None,victim,TO_CHAR)
        return
    # modifiers */
    # size */
    if ch.size < victim.size:
        chance += (ch.size - victim.size) * 10  # bigger = harder to trip */

    # dex */
    chance += ch.get_curr_stat(STAT_DEX)
    chance -= victim.get_curr_stat(STAT_DEX) * 3 // 2

    # speed */
    if IS_SET(ch.off_flags,OFF_FAST) or IS_AFFECTED(ch,AFF_HASTE):
        chance += 10
    if IS_SET(victim.off_flags,OFF_FAST) or IS_AFFECTED(victim,AFF_HASTE):
        chance -= 20
    # level */
    chance += (ch.level - victim.level) * 2
    # now the attack */
    if random.randint(1,99) < chance:
        act("$n trips you and you go down!",ch,None,victim,TO_VICT)
        act("You trip $N and $N goes down!",ch,None,victim,TO_CHAR)
        act("$n trips $N, sending $M to the ground.",ch,None,victim,TO_NOTVICT)
        check_improve(ch,'trip',True,1)

        DAZE_STATE(victim,2 * PULSE_VIOLENCE)
        WAIT_STATE(ch,const.skill_table['trip'].beats)
        victim.position = POS_RESTING
        damage(ch,victim,random.randint(2, 2 +  2 * victim.size),'trip', DAM_BASH,True)
    else:
        damage(ch,victim,0,'trip',DAM_BASH,True)
        WAIT_STATE(ch,const.skill_table['trip'].beats*2 // 3)
        check_improve(ch,'trip',False,1)
    check_killer(ch,victim)

def do_kill( self, argument ):
    ch = self
    argument, arg = read_word(argument)

    if not arg:
        ch.send("Kill whom?\n\r")
        return
    victim = ch.get_char_room(arg)
    if victim == None:
        ch.send("They aren't here.\n\r")
        return
    #  Allow player killing
#    if not IS_NPC(victim):
#        if not IS_SET(victim.act, PLR_KILLER) and not IS_SET(victim.act, PLR_THIEF):
#            ch.send("You must MURDER a player.\n\r")
#            return

    if victim == ch:
        ch.send("You hit yourself.  Ouch!\n\r")
        multi_hit( ch, ch, TYPE_UNDEFINED )
        return
    if is_safe( ch, victim ):
        return
    if victim.fighting and not ch.is_same_group(victim.fighting):
        ch.send("Kill stealing is not permitted.\n\r")
        return
    if IS_AFFECTED(ch, AFF_CHARM) and ch.master == victim:
        act( "$N is your beloved master.", ch, None, victim, TO_CHAR )
        return
    if ch.position == POS_FIGHTING:
        ch.send("You do the best you can!\n\r")
        return

    WAIT_STATE( ch, 1 * PULSE_VIOLENCE )
    check_killer( ch, victim )
    multi_hit( ch, victim, TYPE_UNDEFINED )
    return

def do_murde( self, argument ):
    self.send("If you want to MURDER, spell it out.\n\r")
    return

def do_murder( self, argument ):
    ch = self
    argument, arg = read_word(argument)

    if not arg:
        ch.send("Murder whom?\n\r")
        return

    if IS_AFFECTED(ch,AFF_CHARM) or (IS_NPC(ch) and IS_SET(ch.act,ACT_PET)):
        return
    victim = ch.get_char_room(arg)
    if victim == None:
        ch.send("They aren't here.\n\r")
        return
    if victim == ch:
        ch.send("Suicide is a mortal sin.\n\r")
        return
    if is_safe( ch, victim ):
        return
    if IS_NPC(victim) and victim.fighting and not ch.is_same_group(victim.fighting):
        ch.send("Kill stealing is not permitted.\n\r")
        return
    if IS_AFFECTED(ch, AFF_CHARM) and ch.master == victim:
        act( "$N is your beloved master.", ch, None, victim, TO_CHAR )
        return
    if ch.position == POS_FIGHTING:
        ch.send("You do the best you can!\n\r")
        return

    WAIT_STATE( ch, 1 * PULSE_VIOLENCE )
    if IS_NPC(ch):
        buf = "Help! I am being attacked by %s!" % ch.short_descr
    else:
        buf = "Help!  I am being attacked by %s!" % ch.name
    victim.do_yell(buf)
    check_killer( ch, victim )
    multi_hit( ch, victim, TYPE_UNDEFINED )
    return

def do_backstab( self, argument ):
    ch = self
    argument, arg = read_word(argument)

    if not arg:
        ch.send("Backstab whom?\n\r")
        return
    victim = None
    if ch.fighting:
        ch.send("You're facing the wrong end.\n\r")
        return
    else:
        victim = ch.get_char_room(arg)
        if not victim:
            ch.send("They aren't here.\n\r")
            return
        if victim == ch:
            ch.send("How can you sneak up on yourself?\n\r")
            return

        if is_safe( ch, victim ):
            return

        if IS_NPC(victim) and victim.fighting and not ch.is_same_group(victim.fighting):
            ch.send("Kill stealing is not permitted.\n\r")
            return
        obj = ch.get_eq(WEAR_WIELD)
        if obj:
            ch.send("You need to wield a weapon to backstab.\n\r")
            return
        if victim.hit < victim.max_hit // 3:
            act( "$N is hurt and suspicious ... you can't sneak up.", ch, None, victim, TO_CHAR )
            return
        check_killer( ch, victim )
        WAIT_STATE( ch, const.skill_table['backstab'].beats )
        if random.randint(1,99) < ch.get_skill('backstab') \
        or ( ch.get_skill('backstab') >= 2 and not IS_AWAKE(victim) ):
            check_improve(ch,'backstab',True,1)
            multi_hit( ch, victim, 'backstab' )
        else:
            check_improve(ch,'backstab',False,1)
            damage( ch, victim, 0, 'backstab',DAM_NONE,True)
    return

def do_flee( ch, argument ):
    victim = ch.fighting
    if not victim:
        if ch.position == POS_FIGHTING:
            ch.position = POS_STANDING
        ch.send("You aren't fighting anyone.\n\r")
        return

    was_in = ch.in_room
    for attempt in range(6):
        door = number_door( )
        pexit = was_in.exit[door]
        if not pexit or not pexit.to_room or IS_SET(pexit.exit_info, EX_CLOSED) or random.randint(0,ch.daze) != 0 \
        or ( IS_NPC(ch) and IS_SET(pexit.u1.to_room.room_flags, ROOM_NO_MOB) ):
            continue

        move_char( ch, door, False )
        now_in = ch.in_room
        if  now_in == was_in:
            continue

        ch.in_room = was_in
        act( "$n has fled!", ch, None, None, TO_ROOM )
        ch.in_room = now_in

        if not IS_NPC(ch):
            ch.send("You flee from combat!\n\r")
            if ch.guild.name == 'thief' and (random.randint(1,99) < 3*(ch.level // 2) ):
                ch.send("You snuck away safely.\n\r")
            else:
                ch.send("You lost 10 exp.\n\r") 
                gain_exp( ch, -10 )

        stop_fighting( ch, True )
        return
    ch.send("PANIC! You couldn't escape!\n\r")
    return

def do_rescue( self, argument ):
    ch = self
    argument, arg = read_word(argument)

    if not arg:
        ch.send("Rescue whom?\n\r")
        return
    victim = ch.get_char_room(arg)
    if not victim:
        ch.send("They aren't here.\n\r")
        return
    if victim == ch:
        ch.send("What about fleeing instead?\n\r")
        return
    if not IS_NPC(ch) and IS_NPC(victim):
        ch.send("Doesn't need your help!\n\r")
        return
    if ch.fighting == victim:
        ch.send("Too late.\n\r")
        return
    fch = victim.fighting
    if not fch:
        ch.send("That person is not fighting right now.\n\r")
        return
    if IS_NPC(fch) and not ch.is_same_group(victim):
        ch.send("Kill stealing is not permitted.\n\r")
        return
    WAIT_STATE( ch, const.skill_table['rescue'].beats )
    if random.randint(1,99) > ch.get_skill('rescue'):
        ch.send("You fail the rescue.\n\r")
        check_improve(ch,'rescue',False,1)
        return
    act( "You rescue $N!",  ch, None, victim, TO_CHAR    )
    act( "$n rescues you!", ch, None, victim, TO_VICT    )
    act( "$n rescues $N!",  ch, None, victim, TO_NOTVICT )
    check_improve(ch,'rescue',True,1)

    stop_fighting( fch, False )
    stop_fighting( victim, False )

    check_killer( ch, fch )
    set_fighting( ch, fch )
    set_fighting( fch, ch )
    return

def do_kick( self, argument ):
    ch = self
    if not IS_NPC(ch) and ch.level < const.skill_table['kick'].skill_level[ch.guild.name]:
        ch.send("You better leave the martial arts to fighters.\n\r")
        return
    if IS_NPC(ch) and not IS_SET(ch.off_flags,OFF_KICK):
        return
    victim = ch.fighting
    if not victim:
        ch.send("You aren't fighting anyone.\n\r")
        return

    WAIT_STATE( ch, const.skill_table['kick'].beats )
    if ch.get_skill('kick') > random.randint(1,99):
        damage(ch,victim,random.randint( 1, ch.level ), 'kick',DAM_BASH,True)
        check_improve(ch,'kick',True,1)
    else:
        damage( ch, victim, 0, 'kick',DAM_BASH,True)
        check_improve(ch,'kick',False,1)
    check_killer(ch,victim)
    return

def do_disarm( ch, argument ):
    hth = 0
    chance = ch.get_skill('disarm')
    if chance == 0:
        ch.send("You don't know how to disarm opponents.\n\r")
        return
    hth = ch.get_skill('hand to hand')
    if not ch.get_eq(WEAR_WIELD) \
    and hth == 0 or (IS_NPC(ch) and not IS_SET(ch.off_flags,OFF_DISARM)):
        ch.send("You must wield a weapon to disarm.\n\r")
        return
    victim = ch.fighting
    if not victim:
        ch.send("You aren't fighting anyone.\n\r")
        return
    obj = victim.get_eq(WEAR_WIELD)
    if not obj:
        ch.send("Your opponent is not wielding a weapon.\n\r")
        return

    # find weapon skills */
    ch_weapon = ch.get_weapon_skill(ch.get_weapon_sn())
    vict_weapon = victim.get_weapon_skill(victim.get_weapon_sn())
    ch_vict_weapon = ch.get_weapon_skill(victim.get_weapon_sn())

    # modifiers */

    # skill */
    if ch.get_eq(WEAR_WIELD) == None:
        chance = chance * hth // 150
    else:
        chance = chance * ch_weapon // 100

    chance += (ch_vict_weapon // 2 - vict_weapon) // 2 

    # dex vs. strength */
    chance += ch.get_curr_stat(STAT_DEX)
    chance -= 2 * victim.get_curr_stat(STAT_STR)

    # level */
    chance += (ch.level - victim.level) * 2
 
    # and now the attack */
    if random.randint(1,99) < chance:
        WAIT_STATE( ch, const.skill_table['disarm'].beats )
        disarm( ch, victim )
        check_improve(ch,'disarm',True,1)
    else:
        WAIT_STATE(ch,const.skill_table['disarm'].beats)
        act("You fail to disarm $N.",ch,None,victim,TO_CHAR)
        act("$n tries to disarm you, but fails.",ch,None,victim,TO_VICT)
        act("$n tries to disarm $N, but fails.",ch,None,victim,TO_NOTVICT)
        check_improve(ch,'disarm',False,1)
    check_killer(ch,victim)
    return

def do_sla(ch, argument ):
    ch.send("If you want to SLAY, spell it out.\n\r")
    return

def do_slay( ch, argument ):
    argument, arg = read_word(argument)
    if not arg:
        ch.send("Slay whom?\n\r")
        return
    victim = ch.get_char_room(arg)
    if not victim:
        ch.send("They aren't here.\n\r")
        return
    if ch == victim:
        ch.send("Suicide is a mortal sin.\n\r")
        return
    if not IS_NPC(victim) and victim.level >= ch.get_trust():
        ch.send("You failed.\n\r")
        return
    act( "You slay $M in cold blood!",  ch, None, victim, TO_CHAR    )
    act( "$n slays you in cold blood!", ch, None, victim, TO_VICT    )
    act( "$n slays $N in cold blood!",  ch, None, victim, TO_NOTVICT )
    raw_kill( victim )
    return