Browse Source

pep8 formatting

Bob Mottram 2 months ago
parent
commit
75e66a4e35
13 changed files with 9799 additions and 5898 deletions
  1. 695 568
      abermush.py
  2. 520 517
      cmsg.py
  3. 774 391
      combat.py
  4. 4983 2637
      commands.py
  5. 187 117
      environment.py
  6. 673 244
      events.py
  7. 488 332
      functions.py
  8. 391 390
      mudserver.py
  9. 722 412
      npcs.py
  10. 185 168
      playerconnections.py
  11. 59 45
      proficiencies.py
  12. 42 32
      reaper.py
  13. 80 45
      scheduler.py

File diff suppressed because it is too large
+ 695 - 568
abermush.py


File diff suppressed because it is too large
+ 520 - 517
cmsg.py


File diff suppressed because it is too large
+ 774 - 391
combat.py


File diff suppressed because it is too large
+ 4983 - 2637
commands.py


+ 187 - 117
environment.py

@@ -21,15 +21,21 @@ import time
 
 rainThreshold = 230
 
+
 def runTide():
     """Calculates the tide level as the addition of sine waves
     """
     lunar_orbit_mins = 39312
 
-    daysSinceEpoch=(datetime.datetime.utcnow() - datetime.datetime(1970,1,1)).days
+    daysSinceEpoch = (
+        datetime.datetime.utcnow() -
+        datetime.datetime(
+            1970,
+            1,
+            1)).days
     currHour = datetime.datetime.utcnow().hour
     currMin = datetime.datetime.utcnow().minute
-    timeMins = (daysSinceEpoch*60*24) + (currHour*60) + currMin
+    timeMins = (daysSinceEpoch * 60 * 24) + (currHour * 60) + currMin
 
     lunarMins = timeMins % int(lunar_orbit_mins)
     solarMins = timeMins % int(24 * 60 * 365)
@@ -41,16 +47,38 @@ def runTide():
 
     return daily + lunar + solar
 
+
 def assignTerrainDifficulty(rooms):
     """Updates the terrain difficulty for each room and returns the maximum
     """
-    terrainDifficultyWords=('rock','boulder','slip','steep','rough','volcan','sewer','sand','pebble','mountain','mist','fog','bush','dense','trees','forest','tangle','thick','tough','snow','ice')
-    maxTerrainDifficulty=1
+    terrainDifficultyWords = (
+        'rock',
+        'boulder',
+        'slip',
+        'steep',
+        'rough',
+        'volcan',
+        'sewer',
+        'sand',
+        'pebble',
+        'mountain',
+        'mist',
+        'fog',
+        'bush',
+        'dense',
+        'trees',
+        'forest',
+        'tangle',
+        'thick',
+        'tough',
+        'snow',
+        'ice')
+    maxTerrainDifficulty = 1
     for rm in rooms:
-        difficulty=rooms[rm]['terrainDifficulty']
-        if difficulty==0:
-            roomDescription=rooms[rm]['description'].lower()
-            difficulty=0
+        difficulty = rooms[rm]['terrainDifficulty']
+        if difficulty == 0:
+            roomDescription = rooms[rm]['description'].lower()
+            difficulty = 0
             for w in terrainDifficultyWords:
                 if w in roomDescription:
                     difficulty = difficulty + 1
@@ -59,86 +87,89 @@ def assignTerrainDifficulty(rooms):
             maxTerrainDifficulty = difficulty
     return maxTerrainDifficulty
 
-def assignInitialCoordinates(rooms,rm):
+
+def assignInitialCoordinates(rooms, rm):
     """Sets initial zero room coordinates
     """
-    if len(rooms[rm]['coords'])==0:
-        rooms[rm]['coords'] = [0,0,0]
+    if len(rooms[rm]['coords']) == 0:
+        rooms[rm]['coords'] = [0, 0, 0]
+
 
 def findRoomWithoutCoords(rooms):
     """Finds the next room without assigned coordinates
     """
     for rm in rooms:
         # Room with coords
-        if len(rooms[rm]['coords'])>0:
+        if len(rooms[rm]['coords']) > 0:
             # Search the exits for ones without coords
             for ex in rooms[rm]['exits']:
-                roomID=rooms[rm]['exits'][ex]
+                roomID = rooms[rm]['exits'][ex]
                 rm2 = rooms[rooms[rm]['exits'][ex]]
-                if len(rm2['coords'])==0:
-                    if ex=='north':
+                if len(rm2['coords']) == 0:
+                    if ex == 'north':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][0] = rm2['coords'][0] + 1
                         return rm2
-                    if ex=='northeast':
+                    if ex == 'northeast':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][0] = rm2['coords'][0] + 1
                         rm2['coords'][1] = rm2['coords'][1] - 1
                         return rm2
-                    if ex=='northwest':
+                    if ex == 'northwest':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][0] = rm2['coords'][0] + 1
                         rm2['coords'][1] = rm2['coords'][1] + 1
                         return rm2
-                    if ex=='south':
+                    if ex == 'south':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][0] = rm2['coords'][0] - 1
                         return rm2
-                    if ex=='southeast':
+                    if ex == 'southeast':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][0] = rm2['coords'][0] - 1
                         rm2['coords'][1] = rm2['coords'][1] - 1
                         return rm2
-                    if ex=='southwest':
+                    if ex == 'southwest':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][0] = rm2['coords'][0] - 1
                         rm2['coords'][1] = rm2['coords'][1] + 1
                         return rm2
-                    if ex=='east':
+                    if ex == 'east':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][1] = rm2['coords'][1] - 1
                         return rm2
-                    if ex=='west':
+                    if ex == 'west':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][1] = rm2['coords'][1] + 1
                         return rm2
-                    if ex=='up':
+                    if ex == 'up':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][2] = rm2['coords'][2] + 1
                         return rm2
-                    if ex=='down':
+                    if ex == 'down':
                         rm2['coords'] = rooms[rm]['coords'].copy()
                         rm2['coords'][2] = rm2['coords'][2] - 1
                         return rm2
     for rm in rooms:
         # Room without coords
-        if len(rooms[rm]['coords'])==0:
-            rooms[rm]['coords'] = [0,0,0]
+        if len(rooms[rm]['coords']) == 0:
+            rooms[rm]['coords'] = [0, 0, 0]
             return rooms[rm]
 
     return None
 
+
 def assignCoordinates(rooms):
     """Assigns cartesian coordinates to each room and returns the limits
     """
-    mapArea=[[9999,-9999],[9999,-9999],[9999,-9999]]
-    roomFound=True
+    mapArea = [[9999, -9999], [9999, -9999], [9999, -9999]]
+    roomFound = True
     while roomFound:
         newRoom = findRoomWithoutCoords(rooms)
-        if newRoom == None:
+        if newRoom is None:
             roomFound = False
             break
-        coords=newRoom['coords']
+        coords = newRoom['coords']
         # north/south extent
         if coords[0] > mapArea[0][1]:
             mapArea[0][1] = coords[0]
@@ -156,24 +187,33 @@ def assignCoordinates(rooms):
             mapArea[2][0] = coords[2]
     return mapArea
 
-def highestPointAtCoord(rooms,mapArea,x,y):
+
+def highestPointAtCoord(rooms, mapArea, x, y):
     """Returns the highest elevation at the given location
     """
-    highest=0
+    highest = 0
 
-    vertical_range=mapArea[2][1]-mapArea[2][0]
-    if vertical_range<1:
-        vertical_range=1
+    vertical_range = mapArea[2][1] - mapArea[2][0]
+    if vertical_range < 1:
+        vertical_range = 1
 
     for rm in rooms:
-        if rooms[rm]['coords'][0]-mapArea[0][0]==y:
-            if rooms[rm]['coords'][1]-mapArea[1][0]==x:
-                if rooms[rm]['coords'][2]>highest:
-                    highest=rooms[rm]['coords'][2]
-
-    return (highest-mapArea[2][0])*255/vertical_range
-
-def generateCloud(randnumgen, rooms, mapArea, clouds, cloudGrid, tileSize, windDirection):
+        if rooms[rm]['coords'][0] - mapArea[0][0] == y:
+            if rooms[rm]['coords'][1] - mapArea[1][0] == x:
+                if rooms[rm]['coords'][2] > highest:
+                    highest = rooms[rm]['coords'][2]
+
+    return (highest - mapArea[2][0]) * 255 / vertical_range
+
+
+def generateCloud(
+        randnumgen,
+        rooms,
+        mapArea,
+        clouds,
+        cloudGrid,
+        tileSize,
+        windDirection):
     """Weather simulation
        This uses a simple cloud model adjusted for topology in which
        clouds get smaller as temperature increases and bigger with
@@ -182,31 +222,31 @@ def generateCloud(randnumgen, rooms, mapArea, clouds, cloudGrid, tileSize, windD
     """
     mapWidth = mapArea[1][1] - mapArea[1][0]
     mapHeight = mapArea[0][1] - mapArea[0][0]
-    cloudGridWidth = int(mapWidth/tileSize)
-    cloudGridHeight = int(mapHeight/tileSize)
+    cloudGridWidth = int(mapWidth / tileSize)
+    cloudGridHeight = int(mapHeight / tileSize)
 
-    if len(clouds)==0:
+    if len(clouds) == 0:
         # Generate the clouds map
-        for x in range (0,mapWidth):
-            clouds[x]={}
-            for y in range (0,mapHeight):
-                clouds[x][y]=0
+        for x in range(0, mapWidth):
+            clouds[x] = {}
+            for y in range(0, mapHeight):
+                clouds[x][y] = 0
 
-    if len(cloudGrid)==0:
+    if len(cloudGrid) == 0:
         # Initialize clouds grid randomly
         # This is lower resolution than the map
-        for x in range (0,cloudGridWidth):
-            cloudGrid[x]={}
-            for y in range (0,cloudGridHeight):
-                cloudGrid[x][y]=int(randnumgen.random()*255)
+        for x in range(0, cloudGridWidth):
+            cloudGrid[x] = {}
+            for y in range(0, cloudGridHeight):
+                cloudGrid[x][y] = int(randnumgen.random() * 255)
 
     # Update clouds (same resolution as the map)
-    for x in range (0,mapWidth-1):
+    for x in range(0, mapWidth - 1):
         tile_tx = int(x / tileSize)
         tile_bx = tile_tx + 1
         if tile_bx >= cloudGridWidth:
             tile_bx = 0
-        for y in range (0,mapHeight-1):
+        for y in range(0, mapHeight - 1):
             tile_ty = int(y / tileSize)
             tile_by = tile_ty + 1
             if tile_by >= cloudGridHeight:
@@ -214,99 +254,117 @@ def generateCloud(randnumgen, rooms, mapArea, clouds, cloudGrid, tileSize, windD
 
             interpolate_top = \
                 cloudGrid[tile_tx][tile_ty] + \
-                int((cloudGrid[tile_bx][tile_ty] - cloudGrid[tile_tx][tile_ty]) * \
+                int((cloudGrid[tile_bx][tile_ty] - cloudGrid[tile_tx][tile_ty]) *
                     (x % tileSize) / tileSize)
 
             interpolate_bottom = \
                 cloudGrid[tile_tx][tile_by] + \
-                int((cloudGrid[tile_bx][tile_by] - cloudGrid[tile_tx][tile_by]) * \
+                int((cloudGrid[tile_bx][tile_by] - cloudGrid[tile_tx][tile_by]) *
                     (x % tileSize) / tileSize)
 
             clouds[x][y] = \
                 interpolate_top + \
-                int((interpolate_bottom - interpolate_top) * \
+                int((interpolate_bottom - interpolate_top) *
                     (y % tileSize) / tileSize)
 
     # Clouds change
-    for x in range (0,cloudGridWidth):
-        for y in range (0,cloudGridHeight):
-            cloudGrid[x][y]=cloudGrid[x][y]+(int(randnumgen.random()*11)-5)
+    for x in range(0, cloudGridWidth):
+        for y in range(0, cloudGridHeight):
+            cloudGrid[x][y] = cloudGrid[x][y] + \
+                (int(randnumgen.random() * 11) - 5)
             if cloudGrid[x][y] < 0:
                 cloudGrid[x][y] = cloudGrid[x][y] + 255
             if cloudGrid[x][y] > 255:
                 cloudGrid[x][y] = cloudGrid[x][y] - 255
 
     # change wind direction
-    windDirection = (windDirection + int(randnumgen.random()*9)-4) % 360
+    windDirection = (windDirection + int(randnumgen.random() * 9) - 4) % 360
     if windDirection < 0:
         windDirection = windDirection + 360
 
     # Which directions to shift the clouds
-    dx=0
-    dy=0
-    if windDirection >= 320 or windDirection <=40:
-        dy=1
+    dx = 0
+    dy = 0
+    if windDirection >= 320 or windDirection <= 40:
+        dy = 1
     if windDirection <= 200 and windDirection > 160:
-        dy=-1
+        dy = -1
     if windDirection < 300 and windDirection >= 230:
-        dx=-1
+        dx = -1
     if windDirection > 50 and windDirection <= 130:
-        dx=1
+        dx = 1
 
     # Move clouds in the wind direction
-    cloudGridNew={}
-    for x in range (0,cloudGridWidth):
-        cloudGridNew[x]={}
-        for y in range (0,cloudGridHeight):
-            cloudGridNew[x][y]=cloudGrid[x][y]
+    cloudGridNew = {}
+    for x in range(0, cloudGridWidth):
+        cloudGridNew[x] = {}
+        for y in range(0, cloudGridHeight):
+            cloudGridNew[x][y] = cloudGrid[x][y]
 
-    for x in range (0,cloudGridWidth):
+    for x in range(0, cloudGridWidth):
         old_x = x + dx
-        for y in range (0,cloudGridHeight):
+        for y in range(0, cloudGridHeight):
             old_y = y + dy
-            if old_x >= 0 and old_x <= cloudGridWidth-1 and \
-               old_y >=0 and old_y <= cloudGridHeight-1:
-                cloudGridNew[x][y]=cloudGrid[old_x][old_y]
+            if old_x >= 0 and old_x <= cloudGridWidth - 1 and \
+               old_y >= 0 and old_y <= cloudGridHeight - 1:
+                cloudGridNew[x][y] = cloudGrid[old_x][old_y]
             else:
                 if old_x < 0:
                     old_x = old_x + cloudGridWidth
                 if old_y < 0:
                     old_y = old_y + cloudGridHeight
-                if old_x > cloudGridWidth-1:
+                if old_x > cloudGridWidth - 1:
                     old_x = old_x - cloudGridWidth
-                if old_y > cloudGridHeight-1:
+                if old_y > cloudGridHeight - 1:
                     old_y = old_y - cloudGridHeight
-                cloudGridNew[x][y]=randint(0,255)
+                cloudGridNew[x][y] = randint(0, 255)
 
-    for x in range (0,cloudGridWidth):
-        for y in range (0,cloudGridHeight):
-            cloudGrid[x][y]=cloudGridNew[x][y]
+    for x in range(0, cloudGridWidth):
+        for y in range(0, cloudGridHeight):
+            cloudGrid[x][y] = cloudGridNew[x][y]
 
     return windDirection
 
+
 def getCloudThreshold(temperature):
     """Temperature threshold at which cloud is formed
     """
     return (10 + temperature) * 7
 
+
 def altitudeTemperatureAdjustment(rooms, mapArea, x, y):
     """Temperature decreases with altitude
     """
     return highestPointAtCoord(rooms, mapArea, x, y) * 2.0 / 255.0
 
+
 def terrainTemperatureAdjustment(temperature, rooms, mapArea, x, y):
     """Temperature is adjusted for different types of terrain
     """
-    terrainFreezingWords=('snow','ice')
-    terrainCoolingWords=('rock','steep','sewer','sea','lake','river','stream','water','forest','trees','mist','fog','beach','shore')
-    terrainHeatingWords=('sun','lava','volcan','molten','desert','dry')
-
-    maxTerrainDifficulty=1
+    terrainFreezingWords = ('snow', 'ice')
+    terrainCoolingWords = (
+        'rock',
+        'steep',
+        'sewer',
+        'sea',
+        'lake',
+        'river',
+        'stream',
+        'water',
+        'forest',
+        'trees',
+        'mist',
+        'fog',
+        'beach',
+        'shore')
+    terrainHeatingWords = ('sun', 'lava', 'volcan', 'molten', 'desert', 'dry')
+
+    maxTerrainDifficulty = 1
     for rm in rooms:
-        coords=rooms[rm]['coords']
+        coords = rooms[rm]['coords']
         if coords[0] - mapArea[0][0] == y:
             if coords[1] - mapArea[1][0] == x:
-                roomDescription=rooms[rm]['description'].lower()
+                roomDescription = rooms[rm]['description'].lower()
                 for w in terrainFreezingWords:
                     if w in roomDescription:
                         temperature = temperature * 0.1
@@ -318,6 +376,7 @@ def terrainTemperatureAdjustment(temperature, rooms, mapArea, x, y):
                         temperature = temperature * 1.05
     return temperature
 
+
 def plotClouds(rooms, mapArea, clouds, temperature):
     """Show clouds as ASCII diagram for debugging purposes
     """
@@ -325,53 +384,64 @@ def plotClouds(rooms, mapArea, clouds, temperature):
     mapWidth = mapArea[1][1] - mapArea[1][0]
     mapHeight = mapArea[0][1] - mapArea[0][0]
 
-    for y in range (0,mapHeight-1):
-        lineStr=''
-        for x in range (0,mapWidth-1):
-            mapTemp = clouds[x][y] - (altitudeTemperatureAdjustment(rooms, mapArea, x, y)*7)
-            mapTemp = terrainTemperatureAdjustment(mapTemp, rooms, mapArea, x, y)
-            lineChar='.'
-            if mapTemp>cloudThreshold:
-                lineChar='o'
-            if mapTemp>rainThreshold:
-                lineChar='O'
+    for y in range(0, mapHeight - 1):
+        lineStr = ''
+        for x in range(0, mapWidth - 1):
+            mapTemp = clouds[x][y] - \
+                (altitudeTemperatureAdjustment(rooms, mapArea, x, y) * 7)
+            mapTemp = terrainTemperatureAdjustment(
+                mapTemp, rooms, mapArea, x, y)
+            lineChar = '.'
+            if mapTemp > cloudThreshold:
+                lineChar = 'o'
+            if mapTemp > rainThreshold:
+                lineChar = 'O'
             lineStr = lineStr + lineChar
-        print(lineStr+'\n')
+        print(lineStr + '\n')
     print('\n')
 
+
 def getTemperatureSeasonal():
     """Average temperature for the time of year
     """
     dayOfYear = int(datetime.datetime.utcnow().strftime("%j"))
-    tempFraction=((sin((0.75+(dayOfYear/365.0)) * 2 * 3.1415927)+1)/2.0)
-    return 8 + (7*tempFraction)
+    tempFraction = (
+        (sin((0.75 + (dayOfYear / 365.0)) * 2 * 3.1415927) + 1) / 2.0)
+    return 8 + (7 * tempFraction)
 
-def getTemperature():    
+
+def getTemperature():
     """Average daily seasonal temperature for the universe
     """
-    avTemp=getTemperatureSeasonal()
+    avTemp = getTemperatureSeasonal()
 
-    daysSinceEpoch=(datetime.datetime.utcnow() - datetime.datetime(1970,1,1)).days
+    daysSinceEpoch = (
+        datetime.datetime.utcnow() -
+        datetime.datetime(
+            1970,
+            1,
+            1)).days
 
     # Temperature can vary randomly from one day to the next
     r1 = random.Random(daysSinceEpoch)
-    dailyVariance=avTemp*0.4*(r1.random()-0.5)
+    dailyVariance = avTemp * 0.4 * (r1.random() - 0.5)
 
     # Calculate number of minutes elapsed in the day so far
     currHour = datetime.datetime.utcnow().hour
     currMin = datetime.datetime.utcnow().minute
-    dayMins=(currHour*60)+currMin
+    dayMins = (currHour * 60) + currMin
 
     # Seed number generator for the current minute of the day
-    dayFraction = dayMins/(60.0*24.0)
-    r1 = random.Random((daysSinceEpoch*1440)+dayMins)
+    dayFraction = dayMins / (60.0 * 24.0)
+    r1 = random.Random((daysSinceEpoch * 1440) + dayMins)
 
-    solarVariance=avTemp*0.2
-    solarCycle=sin((0.75+dayFraction)*2*3.1415927)*solarVariance
+    solarVariance = avTemp * 0.2
+    solarCycle = sin((0.75 + dayFraction) * 2 * 3.1415927) * solarVariance
 
     #print("avTemp " + str(avTemp) + " dailyVariance " + str(dailyVariance) + " solarCycle " + str(solarCycle))
     return avTemp + dailyVariance + solarCycle
 
+
 def getTemperatureAtCoords(coords, rooms, mapArea, clouds):
     """Returns the temperature at the given coordinates
     """
@@ -396,4 +466,4 @@ def getTemperatureAtCoords(coords, rooms, mapArea, clouds):
         return currTemp
 
     # with cloud
-    return currTemp*0.8
+    return currTemp * 0.8

File diff suppressed because it is too large
+ 673 - 244
events.py


+ 488 - 332
functions.py

@@ -13,7 +13,8 @@ import commentjson
 import errno
 from copy import deepcopy
 import configparser
-import hashlib, binascii
+import hashlib
+import binascii
 from random import randint
 
 # example of config file usage
@@ -21,151 +22,241 @@ from random import randint
 Config = configparser.ConfigParser()
 Config.read('config.ini')
 
+
 def getSentiment(text, sentimentDB):
-        """Returns a sentiment score for the given text
-           which can be positive or negative
-        """
-        textLower=text.lower()
-        sentiment=0
-        for word,value in sentimentDB.items():
-                if word in textLower:
-                        sentiment = sentiment + value
-        return sentiment
-
-def baselineAffinity(players,id):
-        """Returns the average affinity value for the player
-        """
-        averageAffinity=0
-        ctr=0
-        for name,value in players[id]['affinity'].items():
-                averageAffinity=averageAffinity+value
-                ctr = ctr + 1
-
-        if ctr>0:
-                averageAffinity=int(averageAffinity/ctr)
-
-        if averageAffinity==0:
-                averageAffinity=1
-        return averageAffinity
-
-def increaseAffinityBetweenPlayers(players,id,npcs,p):
-        """Increases the affinity level between two players
-        """
-        # You can't gain affinity with low intelligence creatures
-        # by talking to them
-        if players[id]['int']<2:
-                return
-        # Animals you can't gain affinity with by talking
-        if players[id].get('animalType'):
-                if len(players[id]['animalType'])>0:
-                        return
-        recipientName=npcs[p]['name']
-        if players[id]['affinity'].get(recipientName):
-                players[id]['affinity'][recipientName] = \
-                        players[id]['affinity'][recipientName] + 1
-        else:
-                # set the affinity to an assumed average
-                players[id]['affinity'][recipientName]=baselineAffinity(players,id)
-
-def decreaseAffinityBetweenPlayers(players,id,npcs,p):
-        """Decreases the affinity level between two players
-        """
-        # You can't gain affinity with low intelligence creatures
-        # by talking to them
-        if players[id]['int']<2:
-                return
-        # Animals you can't gain affinity with by talking
-        if players[id].get('animalType'):
-                if len(players[id]['animalType'])>0:
-                        return
-        recipientName=npcs[p]['name']
-        if players[id]['affinity'].get(recipientName):
-                players[id]['affinity'][recipientName] = \
-                        players[id]['affinity'][recipientName] - 1
-
-                # Avoid zero values
-                if players[id]['affinity'][recipientName] == 0:
-                        players[id]['affinity'][recipientName]=-1
-        else:
-                # set the affinity to an assumed average
-                players[id]['affinity'][recipientName]=baselineAffinity(players,id)
-                
+    """Returns a sentiment score for the given text
+       which can be positive or negative
+    """
+    textLower = text.lower()
+    sentiment = 0
+    for word, value in sentimentDB.items():
+        if word in textLower:
+            sentiment = sentiment + value
+    return sentiment
+
+
+def baselineAffinity(players, id):
+    """Returns the average affinity value for the player
+    """
+    averageAffinity = 0
+    ctr = 0
+    for name, value in players[id]['affinity'].items():
+        averageAffinity = averageAffinity + value
+        ctr = ctr + 1
+
+    if ctr > 0:
+        averageAffinity = int(averageAffinity / ctr)
+
+    if averageAffinity == 0:
+        averageAffinity = 1
+    return averageAffinity
+
+
+def increaseAffinityBetweenPlayers(players, id, npcs, p):
+    """Increases the affinity level between two players
+    """
+    # You can't gain affinity with low intelligence creatures
+    # by talking to them
+    if players[id]['int'] < 2:
+        return
+    # Animals you can't gain affinity with by talking
+    if players[id].get('animalType'):
+        if len(players[id]['animalType']) > 0:
+            return
+    recipientName = npcs[p]['name']
+    if players[id]['affinity'].get(recipientName):
+        players[id]['affinity'][recipientName] = \
+            players[id]['affinity'][recipientName] + 1
+    else:
+        # set the affinity to an assumed average
+        players[id]['affinity'][recipientName] = baselineAffinity(players, id)
+
+
+def decreaseAffinityBetweenPlayers(players, id, npcs, p):
+    """Decreases the affinity level between two players
+    """
+    # You can't gain affinity with low intelligence creatures
+    # by talking to them
+    if players[id]['int'] < 2:
+        return
+    # Animals you can't gain affinity with by talking
+    if players[id].get('animalType'):
+        if len(players[id]['animalType']) > 0:
+            return
+    recipientName = npcs[p]['name']
+    if players[id]['affinity'].get(recipientName):
+        players[id]['affinity'][recipientName] = \
+            players[id]['affinity'][recipientName] - 1
+
+        # Avoid zero values
+        if players[id]['affinity'][recipientName] == 0:
+            players[id]['affinity'][recipientName] = -1
+    else:
+        # set the affinity to an assumed average
+        players[id]['affinity'][recipientName] = baselineAffinity(players, id)
+
+
 def randomDescription(descriptionList):
     if '|' in descriptionList:
-        descList=descriptionList.split('|')
-        return descList[randint(0,len(descList)+1)]
+        descList = descriptionList.split('|')
+        return descList[randint(0, len(descList) + 1)]
     return descriptionList
 
+
 def levelUp(id, players, characterClassDB, increment):
-        level = players[id]['lvl']
-        if level < 20:
-                players[id]['exp'] = players[id]['exp'] + increment
-                if players[id]['exp'] > (level+1)*1000:
-                        players[id]['hpMax'] = players[id]['hpMax'] + randint(1,9)
-                        players[id]['lvl'] = level + 1
-                        # remove any existing spell lists
-                        for prof in players[id]['proficiencies']:
-                                if type(prof)==list:
-                                        players[id]['proficiencies'].remove(prof)
-                        # update proficiencies
-                        for prof in characterClassDB[template['characterClass']][str(players[id]['lvl'])]:
-                                if prof not in players[id]['proficiencies']:
-                                        players[id]['proficiencies'].append(prof)
-
-def stowHands(id,players,itemsDB,mud):
-        if int(players[id]['clo_rhand']) > 0:
-                itemID=int(players[id]['clo_rhand'])
-                mud.send_message(id, 'You stow <b234>' + itemsDB[itemID]['article'] + ' ' + itemsDB[itemID]['name'] + '<r>\n\n')
-                players[id]['clo_rhand'] = 0
-
-        if int(players[id]['clo_lhand']) > 0:
-                itemID=int(players[id]['clo_lhand'])
-                mud.send_message(id, 'You stow <b234>' + itemsDB[itemID]['article'] + ' ' + itemsDB[itemID]['name'] + '<r>\n\n')
-                players[id]['clo_lhand'] = 0
+    level = players[id]['lvl']
+    if level < 20:
+        players[id]['exp'] = players[id]['exp'] + increment
+        if players[id]['exp'] > (level + 1) * 1000:
+            players[id]['hpMax'] = players[id]['hpMax'] + randint(1, 9)
+            players[id]['lvl'] = level + 1
+            # remove any existing spell lists
+            for prof in players[id]['proficiencies']:
+                if isinstance(prof, list):
+                    players[id]['proficiencies'].remove(prof)
+            # update proficiencies
+            for prof in characterClassDB[template['characterClass']][str(
+                    players[id]['lvl'])]:
+                if prof not in players[id]['proficiencies']:
+                    players[id]['proficiencies'].append(prof)
+
+
+def stowHands(id, players, itemsDB, mud):
+    if int(players[id]['clo_rhand']) > 0:
+        itemID = int(players[id]['clo_rhand'])
+        mud.send_message(
+            id,
+            'You stow <b234>' +
+            itemsDB[itemID]['article'] +
+            ' ' +
+            itemsDB[itemID]['name'] +
+            '<r>\n\n')
+        players[id]['clo_rhand'] = 0
+
+    if int(players[id]['clo_lhand']) > 0:
+        itemID = int(players[id]['clo_lhand'])
+        mud.send_message(
+            id,
+            'You stow <b234>' +
+            itemsDB[itemID]['article'] +
+            ' ' +
+            itemsDB[itemID]['name'] +
+            '<r>\n\n')
+        players[id]['clo_lhand'] = 0
+
 
 def sizeFromDescription(description):
-        tinyEntity=('tiny','moth','butterfly','insect','beetle','ant','bee','wasp','hornet','mosquito','lizard','mouse','rat','crab','roach','snail','slug','hamster','gerbil')
-        smallEntity=('small','dog','cat','weasel','owl','hawk','crow','rook','robbin','penguin','bird','pidgeon','wolf','badger','fox', 'rat','dwarf','mini','fish','lobster','koala','goblin')
-        largeEntity=('large','tiger','lion','tiger','wolf','leopard','bear','elk','deer','horse','bison','moose','kanga','zebra','oxe','beest','troll','taur')
-        hugeEntity=('huge','ogre','elephant','mastodon','giraffe','titan')
-        gargantuanEntity=('gargantuan','dragon','whale')
-        smallerEntity=('young','child','cub','kitten','puppy','juvenile','kid')
-        description2 = description.lower()
-        size=2
-        for e in tinyEntity:
-                if e in description2:
-                        size=0
-                        break
-        for e in smallEntity:
-                if e in description2:
-                        size=1
-                        break
-        for e in largeEntity:
-                if e in description2:
-                        size=3
-                        break
-        for e in hugeEntity:
-                if e in description2:
-                        size=4
-                        break
-        for e in gargantuanEntity:
-                if e in description2:
-                        size=5
-                        break
-        if size > 0:
-                for e in smallerEntity:
-                        if e in description2:
-                                size = size - 1
-                                break
-              
-        return size
+    tinyEntity = (
+        'tiny',
+        'moth',
+        'butterfly',
+        'insect',
+        'beetle',
+        'ant',
+        'bee',
+        'wasp',
+        'hornet',
+        'mosquito',
+        'lizard',
+        'mouse',
+        'rat',
+        'crab',
+        'roach',
+        'snail',
+        'slug',
+        'hamster',
+        'gerbil')
+    smallEntity = (
+        'small',
+        'dog',
+        'cat',
+        'weasel',
+        'owl',
+        'hawk',
+        'crow',
+        'rook',
+        'robbin',
+        'penguin',
+        'bird',
+        'pidgeon',
+        'wolf',
+        'badger',
+        'fox',
+        'rat',
+        'dwarf',
+        'mini',
+        'fish',
+        'lobster',
+        'koala',
+        'goblin')
+    largeEntity = (
+        'large',
+        'tiger',
+        'lion',
+        'tiger',
+        'wolf',
+        'leopard',
+        'bear',
+        'elk',
+        'deer',
+        'horse',
+        'bison',
+        'moose',
+        'kanga',
+        'zebra',
+        'oxe',
+        'beest',
+        'troll',
+        'taur')
+    hugeEntity = ('huge', 'ogre', 'elephant', 'mastodon', 'giraffe', 'titan')
+    gargantuanEntity = ('gargantuan', 'dragon', 'whale')
+    smallerEntity = (
+        'young',
+        'child',
+        'cub',
+        'kitten',
+        'puppy',
+        'juvenile',
+        'kid')
+    description2 = description.lower()
+    size = 2
+    for e in tinyEntity:
+        if e in description2:
+            size = 0
+            break
+    for e in smallEntity:
+        if e in description2:
+            size = 1
+            break
+    for e in largeEntity:
+        if e in description2:
+            size = 3
+            break
+    for e in hugeEntity:
+        if e in description2:
+            size = 4
+            break
+    for e in gargantuanEntity:
+        if e in description2:
+            size = 5
+            break
+    if size > 0:
+        for e in smallerEntity:
+            if e in description2:
+                size = size - 1
+                break
+
+    return size
+
 
 def updatePlayerAttributes(id, players, itemsDB, itemID, mult):
-        playerAttributes=('luc','per','cha','int','cool','exp')
-        for attr in playerAttributes:
-                players[id][attr] = players[id][attr] + (mult*itemsDB[itemID]['mod_'+attr])
-        # experience returns to zero
-        itemsDB[itemID]['mod_exp']=0
+    playerAttributes = ('luc', 'per', 'cha', 'int', 'cool', 'exp')
+    for attr in playerAttributes:
+        players[id][attr] = players[id][attr] + \
+            (mult * itemsDB[itemID]['mod_' + attr])
+    # experience returns to zero
+    itemsDB[itemID]['mod_exp'] = 0
+
 
 def playerInventoryWeight(id, players, itemsDB):
     """Returns the total weight of a player's inventory
@@ -173,93 +264,110 @@ def playerInventoryWeight(id, players, itemsDB):
     if len(list(players[id]['inv'])) == 0:
         return 0
 
-    weight=0
+    weight = 0
     for i in list(players[id]['inv']):
         weight = weight + itemsDB[int(i)]['weight']
 
     return weight
 
-def moveNPCs(npcs,players,mud,now,nid):
+
+def moveNPCs(npcs, players, mud, now, nid):
     """If movement is defined for an NPC this moves it around
     """
-    if now > npcs[nid]['lastMoved'] + int(npcs[nid]['moveDelay']) + npcs[nid]['randomizer']:
+    if now > npcs[nid]['lastMoved'] + \
+            int(npcs[nid]['moveDelay']) + npcs[nid]['randomizer']:
         # Move types:
         #   random, cycle, inverse cycle, patrol, follow
 
         moveTypeLower = npcs[nid]['moveType'].lower()
 
-        followCycle=False
+        followCycle = False
         if moveTypeLower.startswith('f'):
             if len(npcs[nid]['follow']) == 0:
-                followCycle=True
+                followCycle = True
                 # Look for a player to follow
                 for (pid, pl) in list(players.items()):
                     if npcs[nid]['room'] == players[pid]['room']:
                         # follow by name
                         #print(npcs[nid]['name'] + ' starts following ' + players[pid]['name'] + '\n')
                         npcs[nid]['follow'] = players[pid]['name']
-                        followCycle=False
+                        followCycle = False
             if not followCycle:
                 return
 
-        if moveTypeLower.startswith('c') or moveTypeLower.startswith('p') or followCycle:
+        if moveTypeLower.startswith(
+                'c') or moveTypeLower.startswith('p') or followCycle:
+            npcRoomIndex = 0
+            npcRoomCurr = npcs[nid]['room']
+            for npcRoom in npcs[nid]['path']:
+                if npcRoom == npcRoomCurr:
+                    npcRoomIndex = npcRoomIndex + 1
+                    break
+                npcRoomIndex = npcRoomIndex + 1
+            if npcRoomIndex >= len(npcs[nid]['path']):
+                if moveTypeLower.startswith('p'):
+                    npcs[nid]['moveType'] = 'back'
+                    npcRoomIndex = len(npcs[nid]['path']) - 1
+                    if npcRoomIndex > 0:
+                        npcRoomIndex = npcRoomIndex - 1
+                else:
+                    npcRoomIndex = 0
+        else:
+            if moveTypeLower.startswith('i') or moveTypeLower.startswith('b'):
                 npcRoomIndex = 0
-                npcRoomCurr =npcs[nid]['room']
+                npcRoomCurr = npcs[nid]['room']
                 for npcRoom in npcs[nid]['path']:
-                        if npcRoom == npcRoomCurr:
-                                npcRoomIndex = npcRoomIndex + 1
-                                break
-                        npcRoomIndex = npcRoomIndex + 1
+                    if npcRoom == npcRoomCurr:
+                        npcRoomIndex = npcRoomIndex - 1
+                        break
+                    npcRoomIndex = npcRoomIndex + 1
                 if npcRoomIndex >= len(npcs[nid]['path']):
-                        if moveTypeLower.startswith('p'):
-                                npcs[nid]['moveType'] = 'back'
-                                npcRoomIndex = len(npcs[nid]['path']) - 1
-                                if npcRoomIndex > 0:
-                                        npcRoomIndex = npcRoomIndex - 1
-                        else:
-                                npcRoomIndex = 0
-        else:
-                if moveTypeLower.startswith('i') or moveTypeLower.startswith('b'):
+                    npcRoomIndex = len(npcs[nid]['path']) - 1
+                if npcRoomIndex < 0:
+                    if moveTypeLower.startswith('b'):
+                        npcs[nid]['moveType'] = 'patrol'
                         npcRoomIndex = 0
-                        npcRoomCurr =npcs[nid]['room']
-                        for npcRoom in npcs[nid]['path']:
-                                if npcRoom == npcRoomCurr:
-                                        npcRoomIndex = npcRoomIndex - 1
-                                        break
-                                npcRoomIndex = npcRoomIndex + 1
-                        if npcRoomIndex >= len(npcs[nid]['path']):
-                                npcRoomIndex = len(npcs[nid]['path']) - 1
-                        if npcRoomIndex < 0:
-                                if moveTypeLower.startswith('b'):
-                                        npcs[nid]['moveType'] = 'patrol'
-                                        npcRoomIndex = 0
-                                        if npcRoomIndex < len(npcs[nid]['path'])-1:
-                                                npcRoomIndex = npcRoomIndex + 1
-                                else:
-                                        npcRoomIndex = len(npcs[nid]['path'])-1
-                else:
-                        npcRoomIndex = randint(0, len(npcs[nid]['path']) - 1)
+                        if npcRoomIndex < len(npcs[nid]['path']) - 1:
+                            npcRoomIndex = npcRoomIndex + 1
+                    else:
+                        npcRoomIndex = len(npcs[nid]['path']) - 1
+            else:
+                npcRoomIndex = randint(0, len(npcs[nid]['path']) - 1)
 
         for (pid, pl) in list(players.items()):
-                if npcs[nid]['room'] == players[pid]['room']:
-                        mud.send_message(pid, '<f220>' + npcs[nid]['name'] + "<r> " + npcs[nid]['outDescription'] + "\n\n")
+            if npcs[nid]['room'] == players[pid]['room']:
+                mud.send_message(
+                    pid,
+                    '<f220>' +
+                    npcs[nid]['name'] +
+                    "<r> " +
+                    npcs[nid]['outDescription'] +
+                    "\n\n")
         rm = npcs[nid]['path'][npcRoomIndex]
         npcs[nid]['room'] = rm
         npcs[nid]['lastRoom'] = rm
         for (pid, pl) in list(players.items()):
-                if npcs[nid]['room'] == players[pid]['room']:
-                        mud.send_message(pid, '<f220>' + npcs[nid]['name'] + "<r> " + npcs[nid]['inDescription'] + "\n\n")
+            if npcs[nid]['room'] == players[pid]['room']:
+                mud.send_message(
+                    pid,
+                    '<f220>' +
+                    npcs[nid]['name'] +
+                    "<r> " +
+                    npcs[nid]['inDescription'] +
+                    "\n\n")
         npcs[nid]['randomizer'] = randint(0, npcs[nid]['randomFactor'])
-        npcs[nid]['lastMoved'] =  now
+        npcs[nid]['lastMoved'] = now
+
 
 def hash_password(password):
     """Hash a password for storing."""
     salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
     pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'),
-                                salt, 100000)
+                                  salt, 100000)
     pwdhash = binascii.hexlify(pwdhash)
     return (salt + pwdhash).decode('ascii')
 
+
 def verify_password(stored_password, provided_password):
     """Verify a stored password against one provided by user"""
     salt = stored_password[:64]
@@ -272,137 +380,174 @@ def verify_password(stored_password, provided_password):
     return pwdhash == stored_password
 
 # Function to silently remove file
+
+
 def silentRemove(filename):
     try:
         os.remove(filename)
-    except OSError as e: # this would be "except OSError, e:" before Python 2.6
-        if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory
-            raise # re-raise exception if a different error occurred
+    except OSError as e:  # this would be "except OSError, e:" before Python 2.6
+        if e.errno != errno.ENOENT:  # errno.ENOENT = no such file or directory
+            raise  # re-raise exception if a different error occurred
 
 # Function to load all registered players from JSON files
 # def loadPlayersDB(location = "./players", forceLowercase = True):
-def loadPlayersDB(location = str(Config.get('Players', 'Location')), forceLowercase = True):
-        DB = {}
-        playerFiles = [i for i in os.listdir(location) if os.path.splitext(i)[1] == ".player"]
-        for f in playerFiles:
-                with open(os.path.join(location,f)) as file_object:
-                        #playersDB[f] = file_object.read()
-                        DB[f] = commentjson.load(file_object)
-
-        if forceLowercase is True:
-                out = {}
-                for key, value in DB.items():
-                        out[key.lower()] = value
-
-                return(out)
-        else:
-                return(DB)
 
-        #for i in playersDB:
-                #print(i, playersDB[i])
+
+def loadPlayersDB(
+        location=str(
+            Config.get(
+                'Players',
+                'Location')),
+        forceLowercase=True):
+    DB = {}
+    playerFiles = [i for i in os.listdir(
+        location) if os.path.splitext(i)[1] == ".player"]
+    for f in playerFiles:
+        with open(os.path.join(location, f)) as file_object:
+                #playersDB[f] = file_object.read()
+            DB[f] = commentjson.load(file_object)
+
+    if forceLowercase is True:
+        out = {}
+        for key, value in DB.items():
+            out[key.lower()] = value
+
+        return(out)
+    else:
+        return(DB)
+
+    # for i in playersDB:
+        #print(i, playersDB[i])
 
 # Function used for loggin messages to stdout and a disk file
+
+
 def log(content, type):
-        #logfile = './dum.log'
-        logfile = str(Config.get('Logs', 'ServerLog'))
-        print(str(time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + " [" + type + "] " + content))
-        if os.path.exists(logfile):
-                log = open(logfile, 'a')
-        else:
-                log = open(logfile, 'w')
-        log.write(str(time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + " [" + type + "] " + content) + '\n')
-        log.close()
-
-# Function for returning a first available key value for appending a new element to a dictionary
-def getFreeKey(itemsDict, start = None):
-        if start is None:
-                try:
-                        for x in range(0, len(itemsDict) + 1):
-                                if len(itemsDict[x]) > 0:
-                                        pass
-                except Exception:
-                        pass
-                return(x)
-        else:
-                found = False
-                while found is False:
-                        if start in itemsDict:
-                                start += 1
-                        else:
-                                found = True
-                return(start)
-
-# Function for returning a first available room key value for appending a room element to a dictionary
+    #logfile = './dum.log'
+    logfile = str(Config.get('Logs', 'ServerLog'))
+    print(str(time.strftime("%d/%m/%Y") + " " +
+              time.strftime("%I:%M:%S") + " [" + type + "] " + content))
+    if os.path.exists(logfile):
+        log = open(logfile, 'a')
+    else:
+        log = open(logfile, 'w')
+    log.write(str(time.strftime("%d/%m/%Y") + " " + \
+              time.strftime("%I:%M:%S") + " [" + type + "] " + content) + '\n')
+    log.close()
+
+# Function for returning a first available key value for appending a new
+# element to a dictionary
+
+
+def getFreeKey(itemsDict, start=None):
+    if start is None:
+        try:
+            for x in range(0, len(itemsDict) + 1):
+                if len(itemsDict[x]) > 0:
+                    pass
+        except Exception:
+            pass
+        return(x)
+    else:
+        found = False
+        while found is False:
+            if start in itemsDict:
+                start += 1
+            else:
+                found = True
+        return(start)
+
+# Function for returning a first available room key value for appending a
+# room element to a dictionary
+
+
 def getFreeRoomKey(rooms):
-        maxRoomID=-1
-        for rmkey in rooms.keys():
-                roomIDStr=rmkey.replace('$','').replace('rid=','')
-                if len(roomIDStr)==0:
-                        continue
-
-                roomID=int(roomIDStr)
-                if roomID > maxRoomID:
-                        maxRoomID = roomID
-
-        if maxRoomID>-1:
-                return '$rid=' + str(maxRoomID+1) + '$'
-        return ''
-        
+    maxRoomID = -1
+    for rmkey in rooms.keys():
+        roomIDStr = rmkey.replace('$', '').replace('rid=', '')
+        if len(roomIDStr) == 0:
+            continue
+
+        roomID = int(roomIDStr)
+        if roomID > maxRoomID:
+            maxRoomID = roomID
+
+    if maxRoomID > -1:
+        return '$rid=' + str(maxRoomID + 1) + '$'
+    return ''
+
 # Function for adding events to event scheduler
+
+
 def addToScheduler(eventID, targetID, scheduler, database):
-        if isinstance(eventID, int):
-                for item in database:
-                        if int(item[0]) == eventID:
-                                scheduler[getFreeKey(scheduler)] = { 'time': int(time.time() + int(item[1])), 'target': int(targetID), 'type': item[2], 'body': item[3] }
-        elif isinstance(eventID, str):
-                item = eventID.split('|')
-                scheduler[getFreeKey(scheduler)] = { 'time': int(time.time() + int(item[0])), 'target': int(targetID), 'type': item[1], 'body': item[2] }
+    if isinstance(eventID, int):
+        for item in database:
+            if int(item[0]) == eventID:
+                scheduler[getFreeKey(scheduler)] = {'time': int(time.time(
+                ) + int(item[1])), 'target': int(targetID), 'type': item[2], 'body': item[3]}
+    elif isinstance(eventID, str):
+        item = eventID.split('|')
+        scheduler[getFreeKey(scheduler)] = {'time': int(time.time(
+        ) + int(item[0])), 'target': int(targetID), 'type': item[1], 'body': item[2]}
+
 
 def loadPlayer(name, db):
-        try:
-                #with open(path + name + ".player", "r") as read_file:
-                        #dict = commentjson.load(read_file)
-                        #return(dict)
-                #print(str(db[name.lower() + ".player"]))
-                return(db[name.lower() + ".player"])
-        except Exception:
-                pass
-
-def savePlayer(player, masterDB, savePassword, path = str(Config.get('Players', 'Location')) + "/"):
-        #print(path)
-        DB = loadPlayersDB(forceLowercase = False)
-        for p in DB:
-                if (player['name'] + ".player").lower() == p.lower():
-                        #print("found the file")
-                        #print(p)
-                        with open(path + p, "r") as read_file:
-                                temp = commentjson.load(read_file)
-                        #print(temp)
-                        silentRemove(path + player['name'] + ".player")
-                        #print("removed file")
-                        newPlayer = deepcopy(temp)
-                        #print(newPlayer)
-                        newPlayer['pwd'] = temp['pwd']
-                        for key in newPlayer:
-                                if key != "pwd" or savePassword:
-                                        # print(key)
-                                        newPlayer[key] = player[key]
-
-                        #print(newPlayer)
-                        #print("Saving player state")
-                        with open(path + player['name'] + ".player", 'w') as fp:
-                                commentjson.dump(newPlayer, fp)
-                        #print("Updating playerd DB")
-                        masterDB = loadPlayersDB()
-                        #print(masterDB)
+    try:
+        # with open(path + name + ".player", "r") as read_file:
+            #dict = commentjson.load(read_file)
+            # return(dict)
+        #print(str(db[name.lower() + ".player"]))
+        return(db[name.lower() + ".player"])
+    except Exception:
+        pass
+
+
+def savePlayer(
+        player,
+        masterDB,
+        savePassword,
+        path=str(
+            Config.get(
+                'Players',
+                'Location')) +
+        "/"):
+    # print(path)
+    DB = loadPlayersDB(forceLowercase=False)
+    for p in DB:
+        if (player['name'] + ".player").lower() == p.lower():
+            #print("found the file")
+            # print(p)
+            with open(path + p, "r") as read_file:
+                temp = commentjson.load(read_file)
+            # print(temp)
+            silentRemove(path + player['name'] + ".player")
+            #print("removed file")
+            newPlayer = deepcopy(temp)
+            # print(newPlayer)
+            newPlayer['pwd'] = temp['pwd']
+            for key in newPlayer:
+                if key != "pwd" or savePassword:
+                    # print(key)
+                    newPlayer[key] = player[key]
+
+            # print(newPlayer)
+            #print("Saving player state")
+            with open(path + player['name'] + ".player", 'w') as fp:
+                commentjson.dump(newPlayer, fp)
+            #print("Updating playerd DB")
+            masterDB = loadPlayersDB()
+            # print(masterDB)
 
 # State Save Function
+
+
 def saveState(player, masterDB, savePassword):
-        tempVar = 0
-        savePlayer(player, masterDB, savePassword)                    
-        #masterDB = loadPlayersDB()
+    tempVar = 0
+    savePlayer(player, masterDB, savePassword)
+    #masterDB = loadPlayersDB()
 
-def saveUniverse(rooms,npcsDB,npcs,itemsDB,items,envDB,env):
+
+def saveUniverse(rooms, npcsDB, npcs, itemsDB, items, envDB, env):
     # save rooms
     with open("universe.json", 'w') as fp:
         commentjson.dump(rooms, fp, indent=4, sort_keys=True)
@@ -431,18 +576,22 @@ def saveUniverse(rooms,npcsDB,npcs,itemsDB,items,envDB,env):
     with open("universe_npcsdb.json", 'w') as fp:
         commentjson.dump(npcsDB, fp, indent=4, sort_keys=True)
 
+
 def str2bool(v):
-  return v.lower() in ("yes", "true", "True", "t", "1")
+    return v.lower() in ("yes", "true", "True", "t", "1")
+
 
 def sendToChannel(sender, channel, message, channels):
         #print("Im in!")
-        channels[getFreeKey(channels)] = {"channel": str(channel), "message": str(message), "sender": str(sender)}
-        #print(channels)
+    channels[getFreeKey(channels)] = {"channel": str(
+        channel), "message": str(message), "sender": str(sender)}
+    # print(channels)
+
 
 def loadBlocklist(filename, blocklist):
     if not os.path.isfile(filename):
         return False
-    
+
     blocklist.clear()
 
     blockfile = open(filename, "r")
@@ -455,51 +604,58 @@ def loadBlocklist(filename, blocklist):
                 blockedstr2 = blockedstr2.lower().strip()
                 if blockedstr2 not in blocklist:
                     blocklist.append(blockedstr2)
-        else:    
+        else:
             if blockedstr not in blocklist:
                 blocklist.append(blockedstr)
-            
+
     blockfile.close()
     return True
 
+
 def saveBlocklist(filename, blocklist):
     blockfile = open(filename, "w")
     for blockedstr in blocklist:
         blockfile.write(blockedstr + '\n')
     blockfile.close()
 
-def setRace(template,racesDB,name):
-        """Set the player race
-        """
-        template['speakLanguage'] = racesDB[name]['language'][0]
-        template['language'].clear()
-        template['language'].append(racesDB[name]['language'][0])
-        template['race'] = name
-        template['str'] = racesDB[name]['str']
-        template['siz'] = racesDB[name]['siz']
-        template['per'] = racesDB[name]['per']
-        template['endu'] = racesDB[name]['endu']
-        template['cha'] = racesDB[name]['cha']
-        template['int'] = racesDB[name]['int']
-        template['agi'] = racesDB[name]['agi']
-        template['luc'] = racesDB[name]['luc']
-        template['cool'] = racesDB[name]['cool']
-        template['ref'] = racesDB[name]['ref']
-
-def prepareSpells(mud,id,players):
+
+def setRace(template, racesDB, name):
+    """Set the player race
+    """
+    template['speakLanguage'] = racesDB[name]['language'][0]
+    template['language'].clear()
+    template['language'].append(racesDB[name]['language'][0])
+    template['race'] = name
+    template['str'] = racesDB[name]['str']
+    template['siz'] = racesDB[name]['siz']
+    template['per'] = racesDB[name]['per']
+    template['endu'] = racesDB[name]['endu']
+    template['cha'] = racesDB[name]['cha']
+    template['int'] = racesDB[name]['int']
+    template['agi'] = racesDB[name]['agi']
+    template['luc'] = racesDB[name]['luc']
+    template['cool'] = racesDB[name]['cool']
+    template['ref'] = racesDB[name]['ref']
+
+
+def prepareSpells(mud, id, players):
     """Player prepares spells, called once per second
     """
-    if len(players[id]['prepareSpell'])==0:
-            return
-
-    if players[id]['prepareSpellTime']>0:
-            if players[id]['prepareSpellProgress'] >= players[id]['prepareSpellTime']:
-                    spellName=players[id]['prepareSpell']
-                    players[id]['preparedSpells'][spellName]=1
-                    players[id]['spellSlots'][spellName]=1
-                    players[id]['prepareSpell']=""
-                    players[id]['prepareSpellProgress']=0
-                    players[id]['prepareSpellTime']=0
-                    mud.send_message(id, "You prepared the spell <f220>" + spellName + "<r>\n\n")
-            else:
-                    players[id]['prepareSpellProgress'] = players[id]['prepareSpellProgress'] + 1
+    if len(players[id]['prepareSpell']) == 0:
+        return
+
+    if players[id]['prepareSpellTime'] > 0:
+        if players[id]['prepareSpellProgress'] >= players[id]['prepareSpellTime']:
+            spellName = players[id]['prepareSpell']
+            players[id]['preparedSpells'][spellName] = 1
+            players[id]['spellSlots'][spellName] = 1
+            players[id]['prepareSpell'] = ""
+            players[id]['prepareSpellProgress'] = 0
+            players[id]['prepareSpellTime'] = 0
+            mud.send_message(
+                id,
+                "You prepared the spell <f220>" +
+                spellName +
+                "<r>\n\n")
+        else:
+            players[id]['prepareSpellProgress'] = players[id]['prepareSpellProgress'] + 1

+ 391 - 390
mudserver.py

@@ -23,397 +23,398 @@ import time
 import sys
 from cmsg import cmsg
 
-class MudServer(object):
-        """A basic server for text-based Multi-User Dungeon (MUD) games.
 
-        Once created, the server will listen for players connecting using
-        Telnet. Messages can then be sent to and from multiple connected
-        players.
+class MudServer(object):
+    """A basic server for text-based Multi-User Dungeon (MUD) games.
+
+    Once created, the server will listen for players connecting using
+    Telnet. Messages can then be sent to and from multiple connected
+    players.
+
+    The 'update' method should be called in a loop to keep the server
+    running.
+    """
+
+    # An inner class which is instantiated for each connected client to store
+    # info about them
+
+    class _Client(object):
+        """Holds information about a connected player"""
+
+        # the socket object used to communicate with this client
+        socket = None
+        # the ip address of this client
+        address = ""
+        # holds data send from the client until a full message is received
+        buffer = ""
+        # the last time we checked if the client was still connected
+        lastcheck = 0
+
+        def __init__(self, socket, address, buffer, lastcheck):
+            self.socket = socket
+            self.address = address
+            self.buffer = buffer
+            self.lastcheck = lastcheck
+
+    # Used to store different types of occurences
+    _EVENT_NEW_PLAYER = 1
+    _EVENT_PLAYER_LEFT = 2
+    _EVENT_COMMAND = 3
+
+    # Different states we can be in while reading data from client
+    # See _process_sent_data function
+    _READ_STATE_NORMAL = 1
+    _READ_STATE_COMMAND = 2
+    _READ_STATE_SUBNEG = 3
+
+    # Command codes used by Telnet protocol
+    # See _process_sent_data function
+    _TN_INTERPRET_AS_COMMAND = 255
+    _TN_ARE_YOU_THERE = 246
+    _TN_WILL = 251
+    _TN_WONT = 252
+    _TN_DO = 253
+    _TN_DONT = 254
+    _TN_SUBNEGOTIATION_START = 250
+    _TN_SUBNEGOTIATION_END = 240
+
+    # socket used to listen for new clients
+    _listen_socket = None
+    # holds info on clients. Maps client id to _Client object
+    _clients = {}
+    # counter for assigning each client a new id
+    _nextid = 0
+    # list of occurences waiting to be handled by the code
+    _events = []
+    # list of newly-added occurences
+    _new_events = []
+
+    def __init__(self):
+        """Constructs the MudServer object and starts listening for
+        new players.
+        """
 
-        The 'update' method should be called in a loop to keep the server
-        running.
+        self._clients = {}
+        self._nextid = 0
+        self._events = []
+        self._new_events = []
+
+        # create a new tcp socket which will be used to listen for new clients
+        self._listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+        # set a special option on the socket which allows the port to be
+        # immediately without having to wait
+        self._listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
+                                       1)
+
+        # bind the socket to an ip address and port. Port 23 is the standard
+        # telnet port which telnet clients will use, however on some platforms
+        # this requires root permissions, so we use a higher arbitrary port
+        # number instead: 1234. Address 0.0.0.0 means that we will bind to all
+        # of the available network interfaces
+        self._listen_socket.bind(("0.0.0.0", 35123))
+
+        # set to non-blocking mode. This means that when we call 'accept', it
+        # will return immediately without waiting for a connection
+        self._listen_socket.setblocking(False)
+
+        # start listening for connections on the socket
+        self._listen_socket.listen(1)
+
+    def update(self):
+        """Checks for new players, disconnected players, and new
+        messages sent from players. This method must be called before
+        up-to-date info can be obtained from the 'get_new_players',
+        'get_disconnected_players' and 'get_commands' methods.
+        It should be called in a loop to keep the game running.
         """
 
-        # An inner class which is instantiated for each connected client to store
-        # info about them
-
-        class _Client(object):
-                """Holds information about a connected player"""
-
-                # the socket object used to communicate with this client
-                socket = None
-                # the ip address of this client
-                address = ""
-                # holds data send from the client until a full message is received
-                buffer = ""
-                # the last time we checked if the client was still connected
-                lastcheck = 0
-
-                def __init__(self, socket, address, buffer, lastcheck):
-                        self.socket = socket
-                        self.address = address
-                        self.buffer = buffer
-                        self.lastcheck = lastcheck
-
-        # Used to store different types of occurences
-        _EVENT_NEW_PLAYER = 1
-        _EVENT_PLAYER_LEFT = 2
-        _EVENT_COMMAND = 3
-
-        # Different states we can be in while reading data from client
-        # See _process_sent_data function
-        _READ_STATE_NORMAL = 1
-        _READ_STATE_COMMAND = 2
-        _READ_STATE_SUBNEG = 3
-
-        # Command codes used by Telnet protocol
-        # See _process_sent_data function
-        _TN_INTERPRET_AS_COMMAND = 255
-        _TN_ARE_YOU_THERE = 246
-        _TN_WILL = 251
-        _TN_WONT = 252
-        _TN_DO = 253
-        _TN_DONT = 254
-        _TN_SUBNEGOTIATION_START = 250
-        _TN_SUBNEGOTIATION_END = 240
-
-        # socket used to listen for new clients
-        _listen_socket = None
-        # holds info on clients. Maps client id to _Client object
-        _clients = {}
-        # counter for assigning each client a new id
-        _nextid = 0
-        # list of occurences waiting to be handled by the code
-        _events = []
-        # list of newly-added occurences
-        _new_events = []
-
-        def __init__(self):
-                """Constructs the MudServer object and starts listening for
-                new players.
-                """
-
-                self._clients = {}
-                self._nextid = 0
-                self._events = []
-                self._new_events = []
-
-                # create a new tcp socket which will be used to listen for new clients
-                self._listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-                # set a special option on the socket which allows the port to be
-                # immediately without having to wait
-                self._listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
-                                                                           1)
-
-                # bind the socket to an ip address and port. Port 23 is the standard
-                # telnet port which telnet clients will use, however on some platforms
-                # this requires root permissions, so we use a higher arbitrary port
-                # number instead: 1234. Address 0.0.0.0 means that we will bind to all
-                # of the available network interfaces
-                self._listen_socket.bind(("0.0.0.0", 35123))
-
-                # set to non-blocking mode. This means that when we call 'accept', it
-                # will return immediately without waiting for a connection
-                self._listen_socket.setblocking(False)
-
-                # start listening for connections on the socket
-                self._listen_socket.listen(1)
-
-        def update(self):
-                """Checks for new players, disconnected players, and new
-                messages sent from players. This method must be called before
-                up-to-date info can be obtained from the 'get_new_players',
-                'get_disconnected_players' and 'get_commands' methods.
-                It should be called in a loop to keep the game running.
-                """
-
-                # check for new stuff
-                self._check_for_new_connections()
-                self._check_for_disconnected()
-                self._check_for_messages()
-
-                # move the new events into the main events list so that they can be
-                # obtained with 'get_new_players', 'get_disconnected_players' and
-                # 'get_commands'. The previous events are discarded
-                self._events = list(self._new_events)
-                self._new_events = []
-
-        def get_new_players(self):
-                """Returns a list containing info on any new players that have
-                entered the game since the last call to 'update'. Each item in
-                the list is a player id number.
-                """
-                retval = []
-                # go through all the events in the main list
-                for ev in self._events:
-                        # if the event is a new player occurence, add the info to the list
-                        if ev[0] == self._EVENT_NEW_PLAYER:
-                                retval.append(ev[1])
-                # return the info list
-                return retval
-
-        def get_disconnected_players(self):
-                """Returns a list containing info on any players that have left
-                the game since the last call to 'update'. Each item in the list
-                is a player id number.
-                """
-                retval = []
-                # go through all the events in the main list
-                for ev in self._events:
-                        # if the event is a player disconnect occurence, add the info to
-                        # the list
-                        if ev[0] == self._EVENT_PLAYER_LEFT:
-                                retval.append(ev[1])
-                # return the info list
-                return retval
-
-        def get_commands(self):
-                """Returns a list containing any commands sent from players
-                since the last call to 'update'. Each item in the list is a
-                3-tuple containing the id number of the sending player, a
-                string containing the command (i.e. the first word of what
-                they typed), and another string containing the text after the
-                command
-                """
-                retval = []
-                # go through all the events in the main list
-                for ev in self._events:
-                        # if the event is a command occurence, add the info to the list
-                        if ev[0] == self._EVENT_COMMAND:
-                                retval.append((ev[1], ev[2], ev[3]))
-                # return the info list
-                return retval
-
-        def send_message(self, to, message):
-                """Sends the text in the 'message' parameter to the player with
-                the id number given in the 'to' parameter. The text will be
-                printed out in the player's terminal.
-                """
-                # we make sure to put a newline on the end so the client receives the
-                # message on its own line
-                # print("sending...")
-                # self._attempt_send(to, cmsg(message)+"\n\r")
-                self._attempt_send(to, "\n" + cmsg(message))
-
-        def shutdown(self):
-                """Closes down the server, disconnecting all clients and
-                closing the listen socket.
-                """
-                # for each client
-                for cl in self._clients.values():
-                        # close the socket, disconnecting the client
-                        cl.socket.shutdown()
-                        cl.socket.close()
-                # stop listening for new clients
-                self._listen_socket.close()
-
-        def _attempt_send(self, clid, data):
-                # python 2/3 compatability fix - convert non-unicode string to unicode
-                if sys.version < '3' and type(data) != unicode:
-                        data = unicode(data, "latin1")
-                try:
-                        # look up the client in the client map and use 'sendall' to send
-                        # the message string on the socket. 'sendall' ensures that all of
-                        # the data is sent in one go
-                        self._clients[clid].socket.sendall(bytearray(data, "latin1"))
-                # KeyError will be raised if there is no client with the given id in
-                # the map
-                except KeyError:
-                        pass
-                # If there is a connection problem with the client (e.g. they have
-                # disconnected) a socket error will be raised
-                except socket.error:
-                        self._handle_disconnect(clid)
-
-        def _check_for_new_connections(self):
-
-                # 'select' is used to check whether there is data waiting to be read
-                # from the socket. We pass in 3 lists of sockets, the first being those
-                # to check for readability. It returns 3 lists, the first being
-                # the sockets that are readable. The last parameter is how long to wait
-                # - we pass in 0 so that it returns immediately without waiting
-                rlist, wlist, xlist = select.select([self._listen_socket], [], [], 0)
-
-                # if the socket wasn't in the readable list, there's no data available,
-                # meaning no clients waiting to connect, and so we can exit the method
-                # here
-                if self._listen_socket not in rlist:
-                        return
-
-                # 'accept' returns a new socket and address info which can be used to
-                # communicate with the new client
-                joined_socket, addr = self._listen_socket.accept()
-
-                # set non-blocking mode on the new socket. This means that 'send' and
-                # 'recv' will return immediately without waiting
-                joined_socket.setblocking(False)
-
-                # construct a new _Client object to hold info about the newly connected
-                # client. Use 'nextid' as the new client's id number
-                self._clients[self._nextid] = MudServer._Client(joined_socket, addr[0],
-                                                                "", time.time())
-
-                # add a new player occurence to the new events list with the player's
-                # id number
-                self._new_events.append((self._EVENT_NEW_PLAYER, self._nextid))
-
-                # add 1 to 'nextid' so that the next client to connect will get a
-                # unique id number
-                self._nextid += 1
-
-        def _check_for_disconnected(self):
-
-                # go through all the clients
-                for id, cl in list(self._clients.items()):
-
-                        # if we last checked the client less than 5 seconds ago, skip this
-                        # client and move on to the next one
-                        if time.time() - cl.lastcheck < 5.0:
-                                continue
-
-                        # send the client an invisible character. It doesn't actually
-                        # matter what we send, we're really just checking that data can
-                        # still be written to the socket. If it can't, an error will be
-                        # raised and we'll know that the client has disconnected.
-                        # self._attempt_send(id, "\x00")
-                        self._attempt_send(id, "\x00")
-
-                        # update the last check time
-                        cl.lastcheck = time.time()
-
-        def _check_for_messages(self):
-
-                # go through all the clients
-                for id, cl in list(self._clients.items()):
-
-                        # we use 'select' to test whether there is data waiting to be read
-                        # from the client socket. The function takes 3 lists of sockets,
-                        # the first being those to test for readability. It returns 3 list
-                        # of sockets, the first being those that are actually readable.
-                        rlist, wlist, xlist = select.select([cl.socket], [], [], 0)
-
-                        # if the client socket wasn't in the readable list, there is no
-                        # new data from the client - we can skip it and move on to the next
-                        # one
-                        if cl.socket not in rlist:
-                                continue
-
-                        try:
-                                # read data from the socket, using a max length of 4096
-                                data = cl.socket.recv(4096).decode("latin1")
-
-                                # process the data, stripping out any special Telnet commands
-                                message = self._process_sent_data(cl, data)
-
-                                # if there was a message in the data
-                                if message:
-
-                                        # remove any spaces, tabs etc from the start and end of
-                                        # the message
-                                        message = message.strip()
-
-                                        # separate the message into the command (the first word)
-                                        # and its parameters (the rest of the message)
-                                        command, params = (message.split(" ", 1) + ["", ""])[:2]
-
-                                        # add a command occurence to the new events list with the
-                                        # player's id number, the command and its parameters
-                                        # self._new_events.append((self._EVENT_COMMAND, id,
-                                                                                         # command.lower(), params))
-                                        self._new_events.append((self._EVENT_COMMAND, id,
-                                                                                         command, params))
-
-                        # if there is a problem reading from the socket (e.g. the client
-                        # has disconnected) a socket error will be raised
-                        except socket.error:
-                                self._handle_disconnect(id)
-
-        def _handle_disconnect(self, clid):
-
-                # remove the client from the clients map
-                del(self._clients[clid])
-
-                # add a 'player left' occurence to the new events list, with the
-                # player's id number
-                self._new_events.append((self._EVENT_PLAYER_LEFT, clid))
-
-        def _process_sent_data(self, client, data):
-
-                # the Telnet protocol allows special command codes to be inserted into
-                # messages. For our very simple server we don't need to response to
-                # any of these codes, but we must at least detect and skip over them
-                # so that we don't interpret them as text data.
-                # More info on the Telnet protocol can be found here:
-                # http://pcmicro.com/netfoss/telnet.html
-
-                # start with no message and in the normal state
-                message = None
-                state = self._READ_STATE_NORMAL
-
-                # go through the data a character at a time
-                for c in data:
-
-                        # handle the character differently depending on the state we're in:
-
-                        # normal state
-                        if state == self._READ_STATE_NORMAL:
-
-                                # if we received the special 'interpret as command' code,
-                                # switch to 'command' state so that we handle the next
-                                # character as a command code and not as regular text data
-                                if ord(c) == self._TN_INTERPRET_AS_COMMAND:
-                                        state = self._READ_STATE_COMMAND
-
-                                # if we get a newline character, this is the end of the
-                                # message. Set 'message' to the contents of the buffer and
-                                # clear the buffer
-                                elif c == "\n":
-                                        message = client.buffer
-                                        client.buffer = ""
-
-                                # some telnet clients send the characters as soon as the user
-                                # types them. So if we get a backspace character, this is where
-                                # the user has deleted a character and we should delete the
-                                # last character from the buffer.
-                                elif c == "\x08":
-                                        client.buffer = client.buffer[:-1]
-
-                                # otherwise it's just a regular character - add it to the
-                                # buffer where we're building up the received message
-                                else:
-                                        client.buffer += c
-
-                        # command state
-                        elif state == self._READ_STATE_COMMAND:
-
-                                # the special 'start of subnegotiation' command code indicates
-                                # that the following characters are a list of options until
-                                # we're told otherwise. We switch into 'subnegotiation' state
-                                # to handle this
-                                if ord(c) == self._TN_SUBNEGOTIATION_START:
-                                        state = self._READ_STATE_SUBNEG
-
-                                # if the command code is one of the 'will', 'wont', 'do' or
-                                # 'dont' commands, the following character will be an option
-                                # code so we must remain in the 'command' state
-                                elif ord(c) in (self._TN_WILL, self._TN_WONT, self._TN_DO,
-                                                                self._TN_DONT):
-                                        state = self._READ_STATE_COMMAND
-
-                                # for all other command codes, there is no accompanying data so
-                                # we can return to 'normal' state.
-                                else:
-                                        state = self._READ_STATE_NORMAL
-
-                        # subnegotiation state
-                        elif state == self._READ_STATE_SUBNEG:
-
-                                # if we reach an 'end of subnegotiation' command, this ends the
-                                # list of options and we can return to 'normal' state.
-                                # Otherwise we must remain in this state
-                                if ord(c) == self._TN_SUBNEGOTIATION_END:
-                                        state = self._READ_STATE_NORMAL
-
-                # return the contents of 'message' which is either a string or None
-                return message
+        # check for new stuff
+        self._check_for_new_connections()
+        self._check_for_disconnected()
+        self._check_for_messages()
+
+        # move the new events into the main events list so that they can be
+        # obtained with 'get_new_players', 'get_disconnected_players' and
+        # 'get_commands'. The previous events are discarded
+        self._events = list(self._new_events)
+        self._new_events = []
+
+    def get_new_players(self):
+        """Returns a list containing info on any new players that have
+        entered the game since the last call to 'update'. Each item in
+        the list is a player id number.
+        """
+        retval = []
+        # go through all the events in the main list
+        for ev in self._events:
+            # if the event is a new player occurence, add the info to the list
+            if ev[0] == self._EVENT_NEW_PLAYER:
+                retval.append(ev[1])
+        # return the info list
+        return retval
+
+    def get_disconnected_players(self):
+        """Returns a list containing info on any players that have left
+        the game since the last call to 'update'. Each item in the list
+        is a player id number.
+        """
+        retval = []
+        # go through all the events in the main list
+        for ev in self._events:
+            # if the event is a player disconnect occurence, add the info to
+            # the list
+            if ev[0] == self._EVENT_PLAYER_LEFT:
+                retval.append(ev[1])
+        # return the info list
+        return retval
+
+    def get_commands(self):
+        """Returns a list containing any commands sent from players
+        since the last call to 'update'. Each item in the list is a
+        3-tuple containing the id number of the sending player, a
+        string containing the command (i.e. the first word of what
+        they typed), and another string containing the text after the
+        command
+        """
+        retval = []
+        # go through all the events in the main list
+        for ev in self._events:
+            # if the event is a command occurence, add the info to the list
+            if ev[0] == self._EVENT_COMMAND:
+                retval.append((ev[1], ev[2], ev[3]))
+        # return the info list
+        return retval
+
+    def send_message(self, to, message):
+        """Sends the text in the 'message' parameter to the player with
+        the id number given in the 'to' parameter. The text will be
+        printed out in the player's terminal.
+        """
+        # we make sure to put a newline on the end so the client receives the
+        # message on its own line
+        # print("sending...")
+        # self._attempt_send(to, cmsg(message)+"\n\r")
+        self._attempt_send(to, "\n" + cmsg(message))
+
+    def shutdown(self):
+        """Closes down the server, disconnecting all clients and
+        closing the listen socket.
+        """
+        # for each client
+        for cl in self._clients.values():
+            # close the socket, disconnecting the client
+            cl.socket.shutdown()
+            cl.socket.close()
+        # stop listening for new clients
+        self._listen_socket.close()
+
+    def _attempt_send(self, clid, data):
+        # python 2/3 compatability fix - convert non-unicode string to unicode
+        if sys.version < '3' and not isinstance(data, unicode):
+            data = unicode(data, "latin1")
+        try:
+            # look up the client in the client map and use 'sendall' to send
+            # the message string on the socket. 'sendall' ensures that all of
+            # the data is sent in one go
+            self._clients[clid].socket.sendall(bytearray(data, "latin1"))
+        # KeyError will be raised if there is no client with the given id in
+        # the map
+        except KeyError:
+            pass
+        # If there is a connection problem with the client (e.g. they have
+        # disconnected) a socket error will be raised
+        except socket.error:
+            self._handle_disconnect(clid)
+
+    def _check_for_new_connections(self):
+
+        # 'select' is used to check whether there is data waiting to be read
+        # from the socket. We pass in 3 lists of sockets, the first being those
+        # to check for readability. It returns 3 lists, the first being
+        # the sockets that are readable. The last parameter is how long to wait
+        # - we pass in 0 so that it returns immediately without waiting
+        rlist, wlist, xlist = select.select([self._listen_socket], [], [], 0)
+
+        # if the socket wasn't in the readable list, there's no data available,
+        # meaning no clients waiting to connect, and so we can exit the method
+        # here
+        if self._listen_socket not in rlist:
+            return
+
+        # 'accept' returns a new socket and address info which can be used to
+        # communicate with the new client
+        joined_socket, addr = self._listen_socket.accept()
+
+        # set non-blocking mode on the new socket. This means that 'send' and
+        # 'recv' will return immediately without waiting
+        joined_socket.setblocking(False)
+
+        # construct a new _Client object to hold info about the newly connected
+        # client. Use 'nextid' as the new client's id number
+        self._clients[self._nextid] = MudServer._Client(joined_socket, addr[0],
+                                                        "", time.time())
+
+        # add a new player occurence to the new events list with the player's
+        # id number
+        self._new_events.append((self._EVENT_NEW_PLAYER, self._nextid))
+
+        # add 1 to 'nextid' so that the next client to connect will get a
+        # unique id number
+        self._nextid += 1
+
+    def _check_for_disconnected(self):
+
+        # go through all the clients
+        for id, cl in list(self._clients.items()):
+
+            # if we last checked the client less than 5 seconds ago, skip this
+            # client and move on to the next one
+            if time.time() - cl.lastcheck < 5.0:
+                continue
+
+            # send the client an invisible character. It doesn't actually
+            # matter what we send, we're really just checking that data can
+            # still be written to the socket. If it can't, an error will be
+            # raised and we'll know that the client has disconnected.
+            # self._attempt_send(id, "\x00")
+            self._attempt_send(id, "\x00")
+
+            # update the last check time
+            cl.lastcheck = time.time()
+
+    def _check_for_messages(self):
+
+        # go through all the clients
+        for id, cl in list(self._clients.items()):
+
+            # we use 'select' to test whether there is data waiting to be read
+            # from the client socket. The function takes 3 lists of sockets,
+            # the first being those to test for readability. It returns 3 list
+            # of sockets, the first being those that are actually readable.
+            rlist, wlist, xlist = select.select([cl.socket], [], [], 0)
+
+            # if the client socket wasn't in the readable list, there is no
+            # new data from the client - we can skip it and move on to the next
+            # one
+            if cl.socket not in rlist:
+                continue
+
+            try:
+                # read data from the socket, using a max length of 4096
+                data = cl.socket.recv(4096).decode("latin1")
+
+                # process the data, stripping out any special Telnet commands
+                message = self._process_sent_data(cl, data)
+
+                # if there was a message in the data
+                if message:
+
+                    # remove any spaces, tabs etc from the start and end of
+                    # the message
+                    message = message.strip()
+
+                    # separate the message into the command (the first word)
+                    # and its parameters (the rest of the message)
+                    command, params = (message.split(" ", 1) + ["", ""])[:2]
+
+                    # add a command occurence to the new events list with the
+                    # player's id number, the command and its parameters
+                    # self._new_events.append((self._EVENT_COMMAND, id,
+                    # command.lower(), params))
+                    self._new_events.append((self._EVENT_COMMAND, id,
+                                             command, params))
+
+            # if there is a problem reading from the socket (e.g. the client
+            # has disconnected) a socket error will be raised
+            except socket.error:
+                self._handle_disconnect(id)
+
+    def _handle_disconnect(self, clid):
+
+        # remove the client from the clients map
+        del(self._clients[clid])
+
+        # add a 'player left' occurence to the new events list, with the
+        # player's id number
+        self._new_events.append((self._EVENT_PLAYER_LEFT, clid))
+
+    def _process_sent_data(self, client, data):
+
+        # the Telnet protocol allows special command codes to be inserted into
+        # messages. For our very simple server we don't need to response to
+        # any of these codes, but we must at least detect and skip over them
+        # so that we don't interpret them as text data.
+        # More info on the Telnet protocol can be found here:
+        # http://pcmicro.com/netfoss/telnet.html
+
+        # start with no message and in the normal state
+        message = None
+        state = self._READ_STATE_NORMAL
+
+        # go through the data a character at a time
+        for c in data:
+
+            # handle the character differently depending on the state we're in:
+
+            # normal state
+            if state == self._READ_STATE_NORMAL:
+
+                # if we received the special 'interpret as command' code,
+                # switch to 'command' state so that we handle the next
+                # character as a command code and not as regular text data
+                if ord(c) == self._TN_INTERPRET_AS_COMMAND:
+                    state = self._READ_STATE_COMMAND
+
+                # if we get a newline character, this is the end of the
+                # message. Set 'message' to the contents of the buffer and
+                # clear the buffer
+                elif c == "\n":
+                    message = client.buffer
+                    client.buffer = ""
+
+                # some telnet clients send the characters as soon as the user
+                # types them. So if we get a backspace character, this is where
+                # the user has deleted a character and we should delete the
+                # last character from the buffer.
+                elif c == "\x08":
+                    client.buffer = client.buffer[:-1]
+
+                # otherwise it's just a regular character - add it to the
+                # buffer where we're building up the received message
+                else:
+                    client.buffer += c
+
+            # command state
+            elif state == self._READ_STATE_COMMAND:
+
+                # the special 'start of subnegotiation' command code indicates
+                # that the following characters are a list of options until
+                # we're told otherwise. We switch into 'subnegotiation' state
+                # to handle this
+                if ord(c) == self._TN_SUBNEGOTIATION_START:
+                    state = self._READ_STATE_SUBNEG
+
+                # if the command code is one of the 'will', 'wont', 'do' or
+                # 'dont' commands, the following character will be an option
+                # code so we must remain in the 'command' state
+                elif ord(c) in (self._TN_WILL, self._TN_WONT, self._TN_DO,
+                                self._TN_DONT):
+                    state = self._READ_STATE_COMMAND
+
+                # for all other command codes, there is no accompanying data so
+                # we can return to 'normal' state.
+                else:
+                    state = self._READ_STATE_NORMAL
+
+            # subnegotiation state
+            elif state == self._READ_STATE_SUBNEG:
+
+                # if we reach an 'end of subnegotiation' command, this ends the
+                # list of options and we can return to 'normal' state.
+                # Otherwise we must remain in this state
+                if ord(c) == self._TN_SUBNEGOTIATION_END:
+                    state = self._READ_STATE_NORMAL
+
+        # return the contents of 'message' which is either a string or None
+        return message

File diff suppressed because it is too large
+ 722 - 412
npcs.py


+ 185 - 168
playerconnections.py

@@ -21,176 +21,193 @@ import time
 
 maximum_players = 128
 
-def runNewPlayerConnections(mud,id,players,playersDB,fights,Config):
+
+def runNewPlayerConnections(mud, id, players, playersDB, fights, Config):
     # go through any newly connected players
     for id in mud.get_new_players():
-                if len(players) >= maximum_players:
-                    mud.send_message(id, "Player limit reached\n\n")
-                    mud._handle_disconnect(id)
-
-                # add the new player to the dictionary, noting that they've not been
-                # named yet.
-                # The dictionary key is the player's id number. We set their room to
-                # None initially until they have entered a name
-                # Try adding more player stats - level, gold, inventory, etc
-                players[id] = {
-                        'name': None,
-                        'prefix': None,
-                        'room': None,
-                        'lvl': None,
-                        'exp': None,
-                        'str': None,
-                        'siz': None,
-                        'wei': None,
-                        'per': None,
-                        'endu': None,
-                        'cha': None,
-                        'int': None,
-                        'agi': None,
-                        'luc': None,
-                        'cool': None,
-                        'cred': None,
-                        'inv': None,
-                        'authenticated': None,
-                        'speakLanguage': None,
-                        'language': None,
-                        'clo_head': None,
-                        'clo_neck': None,
-                        'clo_larm': None,
-                        'clo_rarm': None,
-                        'clo_lhand': None,
-                        'clo_rhand': None,
-                        'clo_lwrist': None,
-                        'clo_rwrist': None,
-                        'clo_chest': None,
-                        'clo_lleg': None,
-                        'clo_rleg': None,
-                        'clo_feet': None,
-                        'imp_head': None,
-                        'imp_larm': None,
-                        'imp_rarm': None,
-                        'imp_lhand': None,
-                        'imp_rhand': None,
-                        'imp_chest': None,
-                        'imp_lleg': None,
-                        'imp_rleg': None,
-                        'imp_feet': None,
-                        'hp': None,
-                        'charge': None,
-                        'isInCombat': None,
-                        'lastCombatAction': None,
-                        'isAttackable': None,
-                        'lastRoom': None,
-                        'corpseTTL': None,
-                        'canSay': None,
-                        'canGo': None,
-                        'canLook': None,
-                        'canAttack': None,
-                        'canDirectMessage': None,
-                        'inDescription': None,
-                        'outDescription': None,
-                        'lookDescription': None,
-                        'idleStart': int(time.time()),
-                        'channels': None,
-                        'permissionLevel': None,
-                        'defaultChannel': None,
-                        'exAttribute0': None,
-                        'exAttribute1': None,
-                        'exAttribute2': None,
-                        'ref': None,
-                        'bodyType': None,
-                        'race': None,
-                        'characterClass': None,
-                        'proficiencies': None,
-                        'fightingStyle': None,
-                        'restRequired': None,
-                        'enemy': None,
-                        'archetype': None,
-                        'preparedSpells': None,
-                        'spellSlots': None,
-                        'tempHitPoints': None,
-                        'tempHitPointsStart': None,
-                        'tempHitPointsDuration': None,
-                        'prepareSpell': None,
-                        'prepareSpellProgress': None,
-                        'prepareSpellTime': None,
-                        'frozenDescription': None,
-                        'frozenStart': None,
-                        'frozenDuration': None,
-                        'affinity': None,
-                        'familiar': None
-                }
-
-                # Read in the MOTD file and send to the player
-                motdFile = open(str(Config.get('System', 'Motd')) ,"r")
-                motdLines = motdFile.readlines()
-                motdFile.close()
-                linesCount = len(motdLines)
-                for l in motdLines:
-                        mud.send_message(id, l[:-1])
-
-                if not os.path.isfile(".disableRegistrations"):
-                        mud.send_message(id, '<f15>You can create a new Character, or use the following guest account:')
-                        mud.send_message(id, '<f15>Username: <r><f220>Guest<r><f15> Password: <r><f220>Password')
-                        mud.send_message(id, "\nWhat is your username? (type <f255>new<r> for new character)\n\n")
-                else:
-                        mud.send_message(id, '<f0><b220> New account registrations are currently closed')
-
-                        mud.send_message(id, "\nWhat is your username?\n\n")
-                log("Client ID: " + str(id) + " has connected", "info")
-
-def runPlayerDisconnections(mud,id,players,playersDB,fights,Config):
+        if len(players) >= maximum_players:
+            mud.send_message(id, "Player limit reached\n\n")
+            mud._handle_disconnect(id)
+
+        # add the new player to the dictionary, noting that they've not been
+        # named yet.
+        # The dictionary key is the player's id number. We set their room to
+        # None initially until they have entered a name
+        # Try adding more player stats - level, gold, inventory, etc
+        players[id] = {
+            'name': None,
+            'prefix': None,
+            'room': None,
+            'lvl': None,
+            'exp': None,
+            'str': None,
+            'siz': None,
+            'wei': None,
+            'per': None,
+            'endu': None,
+            'cha': None,
+            'int': None,
+            'agi': None,
+            'luc': None,
+            'cool': None,
+            'cred': None,
+            'inv': None,
+            'authenticated': None,
+            'speakLanguage': None,
+            'language': None,
+            'clo_head': None,
+            'clo_neck': None,
+            'clo_larm': None,
+            'clo_rarm': None,
+            'clo_lhand': None,
+            'clo_rhand': None,
+            'clo_lwrist': None,
+            'clo_rwrist': None,
+            'clo_chest': None,
+            'clo_lleg': None,
+            'clo_rleg': None,
+            'clo_feet': None,
+            'imp_head': None,
+            'imp_larm': None,
+            'imp_rarm': None,
+            'imp_lhand': None,
+            'imp_rhand': None,
+            'imp_chest': None,
+            'imp_lleg': None,
+            'imp_rleg': None,
+            'imp_feet': None,
+            'hp': None,
+            'charge': None,
+            'isInCombat': None,
+            'lastCombatAction': None,
+            'isAttackable': None,
+            'lastRoom': None,
+            'corpseTTL': None,
+            'canSay': None,
+            'canGo': None,
+            'canLook': None,
+            'canAttack': None,
+            'canDirectMessage': None,
+            'inDescription': None,
+            'outDescription': None,
+            'lookDescription': None,
+            'idleStart': int(time.time()),
+            'channels': None,
+            'permissionLevel': None,
+            'defaultChannel': None,
+            'exAttribute0': None,
+            'exAttribute1': None,
+            'exAttribute2': None,
+            'ref': None,
+            'bodyType': None,
+            'race': None,
+            'characterClass': None,
+            'proficiencies': None,
+            'fightingStyle': None,
+            'restRequired': None,
+            'enemy': None,
+            'archetype': None,
+            'preparedSpells': None,
+            'spellSlots': None,
+            'tempHitPoints': None,
+            'tempHitPointsStart': None,
+            'tempHitPointsDuration': None,
+            'prepareSpell': None,
+            'prepareSpellProgress': None,
+            'prepareSpellTime': None,
+            'frozenDescription': None,
+            'frozenStart': None,
+            'frozenDuration': None,
+            'affinity': None,
+            'familiar': None
+        }
+
+        # Read in the MOTD file and send to the player
+        motdFile = open(str(Config.get('System', 'Motd')), "r")
+        motdLines = motdFile.readlines()
+        motdFile.close()
+        linesCount = len(motdLines)
+        for l in motdLines:
+            mud.send_message(id, l[:-1])
+
+        if not os.path.isfile(".disableRegistrations"):
+            mud.send_message(
+                id, '<f15>You can create a new Character, or use the following guest account:')
+            mud.send_message(
+                id, '<f15>Username: <r><f220>Guest<r><f15> Password: <r><f220>Password')
+            mud.send_message(
+                id, "\nWhat is your username? (type <f255>new<r> for new character)\n\n")
+        else:
+            mud.send_message(
+                id, '<f0><b220> New account registrations are currently closed')
+
+            mud.send_message(id, "\nWhat is your username?\n\n")
+        log("Client ID: " + str(id) + " has connected", "info")
+
+
+def runPlayerDisconnections(mud, id, players, playersDB, fights, Config):
     # go through any recently disconnected players
     for id in mud.get_disconnected_players():
 
-                # if for any reason the player isn't in the player map, skip them and
-                # move on to the next one
-                if id not in players:
-                        continue
-
-                log("Player ID: " + str(id) + " has disconnected (" + str(players[id]['name']) + ")", "info")
-
-                # go through all the players in the game
-                for (pid, pl) in list(players.items()):
-                        # send each player a message to tell them about the diconnected
-                        # player if they are in the same room
-                        if players[pid]['authenticated'] is not None:
-                                if players[pid]['authenticated'] is not None \
-                                   and players[pid]['room'] == players[id]['room'] \
-                                   and players[pid]['name'] != players[id]['name']:
-                                        mud.send_message(pid,
-                                                         "<f32><u>{}<r>'s body has vanished.".format(players[id]['name']) + "\n\n")
-
-                # Code here to save player to the database after he's disconnected and before removing him from players dictionary
-                if players[id]['authenticated'] is not None:
-                        log("Player disconnected, saving state", "info")
-                        saveState(players[id], playersDB, False)
-                        playersDB = loadPlayersDB()
-
-                # Create a deep copy of fights, iterate through it and remove fights disconnected player was taking part in
-                fightsCopy = deepcopy(fights)
-                for (fight, pl) in fightsCopy.items():
-                        if fightsCopy[fight]['s1'] == players[id]['name'] or fightsCopy[fight]['s2'] == players[id]['name']:
-                                del fights[fight]
-
-                # remove the player's entry in the player dictionary
-                del players[id]
-
-def runPlayerConnections(mud,id,players,playersDB,fights,Config):
-    runNewPlayerConnections(mud,id,players,playersDB,fights,Config)
-    runPlayerDisconnections(mud,id,players,playersDB,fights,Config)
-
-def disconnectIdlePlayers(mud,players,allowedPlayerIdle):
-        # Evaluate player idle time and disconnect if required
-        now = int(time.time())
-        playersCopy = deepcopy(players)
-        for p in playersCopy:
-                if now - playersCopy[p]['idleStart'] > allowedPlayerIdle:
-                        if players[p]['authenticated'] != None:
-                                mud.send_message(p, "<f232><b11>Your body starts tingling. You instinctively hold your hand up to your face and notice you slowly begin to vanish. You are being disconnected due to inactivity...\n")
-                        else:
-                                mud.send_message(p, "<f232><b11>You are being disconnected due to inactivity. Bye!\n")
-                        log("Character " + str(players[p]['name']) + " is being disconnected due to inactivity.", "warning")
-                        log("Disconnecting client " + str(p), "warning")
-                        del players[p]
-                        mud._handle_disconnect(p)
+        # if for any reason the player isn't in the player map, skip them and
+        # move on to the next one
+        if id not in players:
+            continue
+
+        log("Player ID: " + str(id) + " has disconnected (" +
+            str(players[id]['name']) + ")", "info")
+
+        # go through all the players in the game
+        for (pid, pl) in list(players.items()):
+            # send each player a message to tell them about the diconnected
+            # player if they are in the same room
+            if players[pid]['authenticated'] is not None:
+                if players[pid]['authenticated'] is not None \
+                   and players[pid]['room'] == players[id]['room'] \
+                   and players[pid]['name'] != players[id]['name']:
+                    mud.send_message(
+                        pid, "<f32><u>{}<r>'s body has vanished.".format(
+                            players[id]['name']) + "\n\n")
+
+        # Code here to save player to the database after he's disconnected and
+        # before removing him from players dictionary
+        if players[id]['authenticated'] is not None:
+            log("Player disconnected, saving state", "info")
+            saveState(players[id], playersDB, False)
+            playersDB = loadPlayersDB()
+
+        # Create a deep copy of fights, iterate through it and remove fights
+        # disconnected player was taking part in
+        fightsCopy = deepcopy(fights)
+        for (fight, pl) in fightsCopy.items():
+            if fightsCopy[fight]['s1'] == players[id]['name'] or fightsCopy[fight]['s2'] == players[id]['name']:
+                del fights[fight]
+
+        # remove the player's entry in the player dictionary
+        del players[id]
+
+
+def runPlayerConnections(mud, id, players, playersDB, fights, Config):
+    runNewPlayerConnections(mud, id, players, playersDB, fights, Config)
+    runPlayerDisconnections(mud, id, players, playersDB, fights, Config)
+
+
+def disconnectIdlePlayers(mud, players, allowedPlayerIdle):
+    # Evaluate player idle time and disconnect if required
+    now = int(time.time())
+    playersCopy = deepcopy(players)
+    for p in playersCopy:
+        if now - playersCopy[p]['idleStart'] > allowedPlayerIdle:
+            if players[p]['authenticated'] is not None:
+                mud.send_message(
+                    p,
+                    "<f232><b11>Your body starts tingling. You instinctively hold your hand up to your face and notice you slowly begin to vanish. You are being disconnected due to inactivity...\n")
+            else:
+                mud.send_message(
+                    p, "<f232><b11>You are being disconnected due to inactivity. Bye!\n")
+            log("Character " +
+                str(players[p]['name']) +
+                " is being disconnected due to inactivity.", "warning")
+            log("Disconnecting client " + str(p), "warning")
+            del players[p]
+            mud._handle_disconnect(p)

+ 59 - 45
proficiencies.py

@@ -12,33 +12,37 @@ __status__ = "Production"
 import types
 from random import randint
 
+
 def proficiencyName(prof):
     if '(' not in prof:
         return prof
 
     return prof.split('(')[0].strip()
 
+
 def proficiencyParam(prof):
     if '(' not in prof:
         return 0
 
-    return int(prof.split('(')[1].replace(')','').strip())
+    return int(prof.split('(')[1].replace(')', '').strip())
+
 
-def profFightingStyleDamage(id,players,weaponType,value):
+def profFightingStyleDamage(id, players, weaponType, value):
     if not players[id].get('fightingStyle'):
         return 0
-    fightStyle=players[id]['fightingStyle'].lower()
-    if fightStyle=='archery':
+    fightStyle = players[id]['fightingStyle'].lower()
+    if fightStyle == 'archery':
         if 'bow' in weaponType:
             return 2
     if fightStyle.startswith('duel'):
         if 'ranged' not in weaponType:
             if 'fist' not in weaponType:
-                if players[id]['clo_lhand']==0 or players[id]['clo_rhand']==0:
+                if players[id]['clo_lhand'] == 0 or players[id]['clo_rhand'] == 0:
                     return 2
 
+
 def damageProficiencyItem(prof, id, players, weaponType):
-    if type(prof)==list:
+    if isinstance(prof, list):
         return 0
 
     profName = proficiencyName(prof)
@@ -46,15 +50,15 @@ def damageProficiencyItem(prof, id, players, weaponType):
 
     switcher = {
         "Fighting Style": profFightingStyleDamage,
-#        "Second Wind": profSecondWind,
-#        "Action Surge": profActionSurge,
+        #        "Second Wind": profSecondWind,
+        #        "Action Surge": profActionSurge,
         "Martial Archetype": profMartialArchetype,
         "Ability Score Improvement": profAbilityScore,
         "Extra Attack": profExtraAttack,
         "Martial Archetype feature": profMartialArchetypeFeature,
-#        "Indomitable": profIndomitable,
+        #        "Indomitable": profIndomitable,
         "Spellcasting": profSpellcasting,
-#        "Arcane Recovery": profArcaneRecovery,
+        #        "Arcane Recovery": profArcaneRecovery,
         "Cantrips": profCantrips,
         "Arcane Tradition": profArcaneTradition,
         "Arcane Tradition feature": profArcaneTraditionFeature,
@@ -66,44 +70,48 @@ def damageProficiencyItem(prof, id, players, weaponType):
         return 0
 
     try:
-        return switcher[profName](id,players,weaponType,profValue)
+        return switcher[profName](id, players, weaponType, profValue)
     except Exception as e:
         print("damageProficiencyItem error " + prof)
 
-    return 0    
+    return 0
+
 
 def damageProficiency(id, players, weaponType, characterClassDB):
     if not players[id].get('race'):
         return 0
 
-    playerRace=players[id]['race']
-    
+    playerRace = players[id]['race']
+
     if not characterClassDB.get(playerRace):
         return 0
 
-    damage=0
-    for lvl in range(1,players[id]['lvl']):
+    damage = 0
+    for lvl in range(1, players[id]['lvl']):
         if characterClassDB[playerRace].get(str(lvl)):
-            profList=characterClassDB[playerRace][str(lvl)]
+            profList = characterClassDB[playerRace][str(lvl)]
             for p in profList:
                 damage = damage + \
                     damageProficiencyItem(p, id, players, weaponType)
     return damage
 
-def profSecondWind(id,players,profValue):
+
+def profSecondWind(id, players, profValue):
     if players[id]['restRequired'] != 0:
         return 0
-    players[id]['restRequired']=1
-    return randint(1,10)
+    players[id]['restRequired'] = 1
+    return randint(1, 10)
 
-def profIndomitable(id,players,profValue):
+
+def profIndomitable(id, players, profValue):
     if players[id]['restRequired'] != 0:
         return 0
-    players[id]['restRequired']=1
-    return randint(1,10)
+    players[id]['restRequired'] = 1
+    return randint(1, 10)
+
 
 def defenseProficiencyItem(prof, id, players):
-    if type(prof)==list:
+    if isinstance(prof, list):
         return 0
 
     profName = proficiencyName(prof)
@@ -119,73 +127,77 @@ def defenseProficiencyItem(prof, id, players):
         return 0
 
     try:
-        return switcher[profName](id,players,profValue)
+        return switcher[profName](id, players, profValue)
     except Exception as e:
         print("defenseProficiencyItem error " + prof)
 
-    return 0    
+    return 0
+
 
 def defenseProficiency(id, players, characterClassDB):
     if not players[id].get('race'):
         return 0
 
-    playerRace=players[id]['race']
-