functions.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. __filename__ = "functions.py"
  2. __author__ = "Bob Mottram"
  3. __credits__ = ["Bartek Radwanski"]
  4. __license__ = "AGPL3+"
  5. __version__ = "1.0.0"
  6. __maintainer__ = "Bob Mottram"
  7. __email__ = "bob@freedombone.net"
  8. __status__ = "Production"
  9. import time
  10. import os
  11. import json
  12. import errno
  13. from copy import deepcopy
  14. import configparser
  15. import hashlib
  16. import binascii
  17. from random import randint
  18. # example of config file usage
  19. # print(str(Config.get('Database', 'Hostname')))
  20. Config = configparser.ConfigParser()
  21. Config.read('config.ini')
  22. wearLocation = ('head', 'neck', 'lwrist', 'rwrist', 'larm', 'rarm',
  23. 'chest', 'feet', 'lfinger', 'rfinger', 'back',
  24. 'lleg', 'rleg')
  25. def TimeStringToSec(durationStr: str) -> int:
  26. """Converts a description of a duration such as '1 hour'
  27. into a number of seconds
  28. """
  29. if ' ' not in durationStr:
  30. return 1
  31. dur = durationStr.lower().split(' ')
  32. if dur[1].startswith('s'):
  33. return int(dur[0])
  34. if dur[1].startswith('min'):
  35. return int(dur[0]) * 60
  36. if dur[1].startswith('hour') or dur[1].startswith('hr'):
  37. return int(dur[0]) * 60 * 60
  38. if dur[1].startswith('day'):
  39. return int(dur[0]) * 60 * 60 * 24
  40. return 1
  41. def getSentiment(text: str, sentimentDB: {}) -> int:
  42. """Returns a sentiment score for the given text
  43. which can be positive or negative
  44. """
  45. textLower = text.lower()
  46. sentiment = 0
  47. for word, value in sentimentDB.items():
  48. if word in textLower:
  49. sentiment = sentiment + value
  50. return sentiment
  51. def getGuildSentiment(players: {}, id, npcs: {}, p, guilds: {}) -> int:
  52. """Returns the sentiment of the guild of the first player
  53. towards the second
  54. """
  55. if not players[id].get('guild'):
  56. return 0
  57. if not players[id].get('guildRole'):
  58. return 0
  59. guildName = players[id]['guild']
  60. if not guilds.get(guildName):
  61. return 0
  62. otherPlayerName = npcs[p]['name']
  63. if not guilds[guildName]['affinity'].get(otherPlayerName):
  64. return 0
  65. # NOTE: this could be adjusted by the strength of affinity
  66. # between the player and the guild, but for now it's just hardcoded
  67. return int(guilds[guildName]['affinity'][otherPlayerName] / 2)
  68. def baselineAffinity(players, id):
  69. """Returns the average affinity value for the player
  70. """
  71. averageAffinity = 0
  72. ctr = 0
  73. for name, value in players[id]['affinity'].items():
  74. averageAffinity = averageAffinity + value
  75. ctr += 1
  76. if ctr > 0:
  77. averageAffinity = int(averageAffinity / ctr)
  78. if averageAffinity == 0:
  79. averageAffinity = 1
  80. return averageAffinity
  81. def increaseAffinityBetweenPlayers(players: {}, id, npcs: {},
  82. p, guilds: {}) -> None:
  83. """Increases the affinity level between two players
  84. """
  85. # You can't gain affinity with low intelligence creatures
  86. # by talking to them
  87. if players[id]['int'] < 2:
  88. return
  89. # Animals you can't gain affinity with by talking
  90. if players[id].get('animalType'):
  91. if len(players[id]['animalType']) > 0:
  92. return
  93. max_affinity = 10
  94. recipientName = npcs[p]['name']
  95. if players[id]['affinity'].get(recipientName):
  96. if players[id]['affinity'][recipientName] < max_affinity:
  97. players[id]['affinity'][recipientName] += 1
  98. else:
  99. # set the affinity to an assumed average
  100. players[id]['affinity'][recipientName] = baselineAffinity(players, id)
  101. # adjust guild affinity
  102. if players[id].get('guild') and players[id].get('guildRole'):
  103. guildName = players[id]['guild']
  104. if guilds.get(guildName):
  105. guild = guilds[guildName]
  106. if guild['affinity'].get(recipientName):
  107. if guild['affinity'][recipientName] < max_affinity:
  108. guild['affinity'][recipientName] += 1
  109. else:
  110. guild['affinity'][recipientName] = \
  111. baselineAffinity(players, id)
  112. def decreaseAffinityBetweenPlayers(players: {}, id, npcs: {},
  113. p, guilds: {}) -> None:
  114. """Decreases the affinity level between two players
  115. """
  116. # You can't gain affinity with low intelligence creatures
  117. # by talking to them
  118. if players[id]['int'] < 2:
  119. return
  120. # Animals you can't gain affinity with by talking
  121. if players[id].get('animalType'):
  122. if len(players[id]['animalType']) > 0:
  123. return
  124. min_affinity = -10
  125. recipientName = npcs[p]['name']
  126. if players[id]['affinity'].get(recipientName):
  127. if players[id]['affinity'][recipientName] > min_affinity:
  128. players[id]['affinity'][recipientName] -= 1
  129. # Avoid zero values
  130. if players[id]['affinity'][recipientName] == 0:
  131. players[id]['affinity'][recipientName] = -1
  132. else:
  133. # set the affinity to an assumed average
  134. players[id]['affinity'][recipientName] = baselineAffinity(players, id)
  135. # adjust guild affinity
  136. if players[id].get('guild') and players[id].get('guildRole'):
  137. guildName = players[id]['guild']
  138. if guilds.get(guildName):
  139. guild = guilds[guildName]
  140. if guild['affinity'].get(recipientName):
  141. if guild['affinity'][recipientName] > min_affinity:
  142. guild['affinity'][recipientName] -= 1
  143. # Avoid zero values
  144. if guild['affinity'][recipientName] == 0:
  145. guild['affinity'][recipientName] = -1
  146. else:
  147. guild['affinity'][recipientName] = \
  148. baselineAffinity(players, id)
  149. def randomDescription(descriptionList):
  150. if '|' in descriptionList:
  151. descList = descriptionList.split('|')
  152. return descList[randint(0, len(descList) - 1)]
  153. return descriptionList
  154. def levelUp(id, players, characterClassDB, increment):
  155. level = players[id]['lvl']
  156. if level < 20:
  157. players[id]['exp'] = players[id]['exp'] + increment
  158. if players[id]['exp'] > (level + 1) * 1000:
  159. players[id]['hpMax'] = players[id]['hpMax'] + randint(1, 9)
  160. players[id]['lvl'] = level + 1
  161. # remove any existing spell lists
  162. for prof in players[id]['proficiencies']:
  163. if isinstance(prof, list):
  164. players[id]['proficiencies'].remove(prof)
  165. # update proficiencies
  166. idx = players[id]['characterClass']
  167. for prof in characterClassDB[idx][str(players[id]['lvl'])]:
  168. if prof not in players[id]['proficiencies']:
  169. players[id]['proficiencies'].append(prof)
  170. def stowHands(id, players: {}, itemsDB: {}, mud):
  171. if int(players[id]['clo_rhand']) > 0:
  172. itemID = int(players[id]['clo_rhand'])
  173. mud.sendMessage(
  174. id, 'You stow <b234>' +
  175. itemsDB[itemID]['article'] +
  176. ' ' +
  177. itemsDB[itemID]['name'] +
  178. '<r>\n\n')
  179. players[id]['clo_rhand'] = 0
  180. if int(players[id]['clo_lhand']) > 0:
  181. itemID = int(players[id]['clo_lhand'])
  182. mud.sendMessage(
  183. id, 'You stow <b234>' +
  184. itemsDB[itemID]['article'] +
  185. ' ' +
  186. itemsDB[itemID]['name'] +
  187. '<r>\n\n')
  188. players[id]['clo_lhand'] = 0
  189. def sizeFromDescription(description: str):
  190. tinyEntity = ('tiny', 'moth', 'butterfly', 'insect', 'beetle', 'ant',
  191. 'bee', 'wasp', 'hornet', 'mosquito', 'lizard', 'mouse',
  192. 'rat', 'crab', 'roach', 'snail', 'slug', 'hamster',
  193. 'gerbil')
  194. smallEntity = ('small', 'dog', 'cat', 'weasel', 'otter', 'owl', 'hawk',
  195. 'crow', 'rook', 'robin', 'penguin', 'bird', 'pidgeon',
  196. 'wolf', 'badger', 'fox', 'rat', 'dwarf', 'mini', 'fish',
  197. 'lobster', 'koala', 'goblin', 'hare')
  198. largeEntity = ('large', 'tiger', 'lion', 'tiger', 'wolf', 'leopard',
  199. 'bear', 'elk', 'deer', 'horse', 'bison', 'moose',
  200. 'kanga', 'zebra', 'oxe', 'beest', 'troll', 'taur')
  201. hugeEntity = ('huge', 'ogre', 'elephant', 'mastodon', 'giraffe', 'titan')
  202. gargantuanEntity = ('gargantuan', 'dragon', 'whale')
  203. smallerEntity = ('young', 'child', 'cub', 'kitten', 'puppy', 'juvenile',
  204. 'kid')
  205. description2 = description.lower()
  206. size = 2
  207. for e in tinyEntity:
  208. if e in description2:
  209. size = 0
  210. break
  211. for e in smallEntity:
  212. if e in description2:
  213. size = 1
  214. break
  215. for e in largeEntity:
  216. if e in description2:
  217. size = 3
  218. break
  219. for e in hugeEntity:
  220. if e in description2:
  221. size = 4
  222. break
  223. for e in gargantuanEntity:
  224. if e in description2:
  225. size = 5
  226. break
  227. if size > 0:
  228. for e in smallerEntity:
  229. if e in description2:
  230. size = size - 1
  231. break
  232. return size
  233. def updatePlayerAttributes(id, players, itemsDB, itemID, mult):
  234. playerAttributes = ('luc', 'per', 'cha', 'int', 'cool', 'exp')
  235. for attr in playerAttributes:
  236. players[id][attr] = \
  237. players[id][attr] + \
  238. (mult * itemsDB[itemID]['mod_' + attr])
  239. # experience returns to zero
  240. itemsDB[itemID]['mod_exp'] = 0
  241. def playerInventoryWeight(id, players, itemsDB):
  242. """Returns the total weight of a player's inventory
  243. """
  244. if len(list(players[id]['inv'])) == 0:
  245. return 0
  246. weight = 0
  247. for i in list(players[id]['inv']):
  248. weight = weight + itemsDB[int(i)]['weight']
  249. return weight
  250. def hash_password(password):
  251. """Hash a password for storing."""
  252. salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
  253. pwdhash = \
  254. hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'),
  255. salt, 100000)
  256. pwdhash = binascii.hexlify(pwdhash)
  257. return (salt + pwdhash).decode('ascii')
  258. def verify_password(stored_password, provided_password):
  259. """Verify a stored password against one provided by user"""
  260. salt = stored_password[:64]
  261. stored_password = stored_password[64:]
  262. pwdhash = \
  263. hashlib.pbkdf2_hmac('sha512',
  264. provided_password.encode('utf-8'),
  265. salt.encode('ascii'), 100000)
  266. pwdhash = binascii.hexlify(pwdhash).decode('ascii')
  267. return pwdhash == stored_password
  268. def silentRemove(filename: str):
  269. """Function to silently remove file
  270. """
  271. try:
  272. os.remove(filename)
  273. except OSError as e:
  274. if e.errno != errno.ENOENT:
  275. raise
  276. def loadPlayersDB(location=str(Config.get('Players', 'Location')),
  277. forceLowercase=True):
  278. """Function to load all registered players from JSON files
  279. """
  280. DB = {}
  281. playerFiles = \
  282. [i for i in os.listdir(location)
  283. if os.path.splitext(i)[1] == ".player"]
  284. for f in playerFiles:
  285. with open(os.path.join(location, f)) as file_object:
  286. DB[f] = json.loads(file_object.read())
  287. if forceLowercase is True:
  288. out = {}
  289. for key, value in DB.items():
  290. out[key.lower()] = value
  291. return(out)
  292. else:
  293. return(DB)
  294. # for i in playersDB:
  295. # print(i, playersDB[i])
  296. def log(content, type):
  297. """Function used for logging messages to stdout and a disk file
  298. """
  299. # logfile = './dum.log'
  300. logfile = str(Config.get('Logs', 'ServerLog'))
  301. print(str(time.strftime("%d/%m/%Y") + " " +
  302. time.strftime("%I:%M:%S") + " [" + type + "] " + content))
  303. if os.path.exists(logfile):
  304. log = open(logfile, 'a')
  305. else:
  306. log = open(logfile, 'w')
  307. log.write(str(time.strftime("%d/%m/%Y") + " " +
  308. time.strftime("%I:%M:%S") + " [" + type + "] " + content) + '\n')
  309. log.close()
  310. def getFreeKey(itemsDict, start=None):
  311. """Function for returning a first available key value for appending a new
  312. element to a dictionary
  313. """
  314. if start is None:
  315. try:
  316. for x in range(0, len(itemsDict) + 1):
  317. if len(itemsDict[x]) > 0:
  318. pass
  319. except Exception:
  320. pass
  321. return(x)
  322. else:
  323. found = False
  324. while found is False:
  325. if start in itemsDict:
  326. start += 1
  327. else:
  328. found = True
  329. return(start)
  330. def getFreeRoomKey(rooms):
  331. """Function for returning a first available room key value for appending a
  332. room element to a dictionary
  333. """
  334. maxRoomID = -1
  335. for rmkey in rooms.keys():
  336. roomIDStr = rmkey.replace('$', '').replace('rid=', '')
  337. if len(roomIDStr) == 0:
  338. continue
  339. roomID = int(roomIDStr)
  340. if roomID > maxRoomID:
  341. maxRoomID = roomID
  342. if maxRoomID > -1:
  343. return '$rid=' + str(maxRoomID + 1) + '$'
  344. return ''
  345. def addToScheduler(eventID, targetID, scheduler, database):
  346. """Function for adding events to event scheduler
  347. """
  348. if isinstance(eventID, int):
  349. for item in database:
  350. if int(item[0]) == eventID:
  351. scheduler[getFreeKey(scheduler)] = {
  352. 'time': int(time.time() + int(item[1])),
  353. 'target': int(targetID),
  354. 'type': item[2],
  355. 'body': item[3]
  356. }
  357. elif isinstance(eventID, str):
  358. item = eventID.split('|')
  359. scheduler[getFreeKey(scheduler)] = {
  360. 'time': int(time.time() + int(item[0])),
  361. 'target': int(targetID),
  362. 'type': item[1],
  363. 'body': item[2]
  364. }
  365. def loadPlayer(name: str,
  366. location=str(Config.get('Players',
  367. 'Location'))) -> dict:
  368. try:
  369. with open(os.path.join(location, name + ".player"), "r") as read_file:
  370. playerDict = json.loads(read_file.read())
  371. return(playerDict)
  372. except Exception:
  373. pass
  374. return {}
  375. def savePlayer(player, masterDB, savePassword,
  376. path=str(Config.get('Players', 'Location')) + "/"):
  377. DB = loadPlayersDB(forceLowercase=False)
  378. for p in DB:
  379. if (player['name'] + ".player").lower() == p.lower():
  380. with open(path + p, "r") as read_file:
  381. temp = json.loads(read_file.read())
  382. silentRemove(path + player['name'] + ".player")
  383. newPlayer = deepcopy(temp)
  384. newPlayer['pwd'] = temp['pwd']
  385. for key in newPlayer:
  386. if key != "pwd" or savePassword:
  387. if player.get(key):
  388. newPlayer[key] = player[key]
  389. with open(path + player['name'] + ".player", 'w') as fp:
  390. fp.write(json.dumps(newPlayer))
  391. loadPlayersDB()
  392. def saveState(player, masterDB, savePassword):
  393. savePlayer(player, masterDB, savePassword)
  394. # masterDB = loadPlayersDB()
  395. def saveUniverse(rooms: {}, npcsDB: {}, npcs: {},
  396. itemsDB: {}, items: {},
  397. envDB: {}, env: {}, guildsDB: {}):
  398. # save rooms
  399. if os.path.isfile('universe.json'):
  400. os.rename('universe.json', 'universe2.json')
  401. with open("universe.json", 'w') as fp:
  402. fp.write(json.dumps(rooms))
  403. # save items
  404. if os.path.isfile('universe_items.json'):
  405. os.rename('universe_items.json', 'universe_items2.json')
  406. with open("universe_items.json", 'w') as fp:
  407. fp.write(json.dumps(items))
  408. # save itemsDB
  409. if os.path.isfile('universe_itemsdb.json'):
  410. os.rename('universe_itemsdb.json', 'universe_itemsdb2.json')
  411. with open("universe_itemsdb.json", 'w') as fp:
  412. fp.write(json.dumps(itemsDB))
  413. # save environment actors
  414. if os.path.isfile('universe_actorsdb.json'):
  415. os.rename('universe_actorsdb.json', 'universe_actorsdb2.json')
  416. with open("universe_actorsdb.json", 'w') as fp:
  417. fp.write(json.dumps(envDB))
  418. # save environment actors
  419. if os.path.isfile('universe_actors.json'):
  420. os.rename('universe_actors.json', 'universe_actors2.json')
  421. with open("universe_actors.json", 'w') as fp:
  422. fp.write(json.dumps(env))
  423. # save npcs
  424. if os.path.isfile('universe_npcs.json'):
  425. os.rename('universe_npcs.json', 'universe_npcs2.json')
  426. with open("universe_npcs.json", 'w') as fp:
  427. fp.write(json.dumps(npcs))
  428. # save npcsDB
  429. if os.path.isfile('universe_npcsdb.json'):
  430. os.rename('universe_npcsdb.json', 'universe_npcsdb2.json')
  431. with open("universe_npcsdb.json", 'w') as fp:
  432. fp.write(json.dumps(npcsDB))
  433. def str2bool(v):
  434. return v.lower() in ("yes", "true", "True", "t", "1")
  435. def sendToChannel(sender, channel, message, channels):
  436. # print("Im in!")
  437. channels[getFreeKey(channels)] = {
  438. "channel": str(channel),
  439. "message": str(message),
  440. "sender": str(sender)}
  441. # print(channels)
  442. def loadBlocklist(filename, blocklist):
  443. if not os.path.isfile(filename):
  444. return False
  445. blocklist.clear()
  446. blockfile = open(filename, "r")
  447. for line in blockfile:
  448. blockedstr = line.lower().strip()
  449. if ',' in blockedstr:
  450. blockedlst = blockedstr.lower().strip().split(',')
  451. for blockedstr2 in blockedlst:
  452. blockedstr2 = blockedstr2.lower().strip()
  453. if blockedstr2 not in blocklist:
  454. blocklist.append(blockedstr2)
  455. else:
  456. if blockedstr not in blocklist:
  457. blocklist.append(blockedstr)
  458. blockfile.close()
  459. return True
  460. def saveBlocklist(filename, blocklist):
  461. blockfile = open(filename, "w")
  462. for blockedstr in blocklist:
  463. blockfile.write(blockedstr + '\n')
  464. blockfile.close()
  465. def setRace(template, racesDB, name):
  466. """Set the player race
  467. """
  468. template['speakLanguage'] = racesDB[name]['language'][0]
  469. template['language'].clear()
  470. template['language'].append(racesDB[name]['language'][0])
  471. template['race'] = name
  472. template['str'] = racesDB[name]['str']
  473. template['siz'] = racesDB[name]['siz']
  474. template['per'] = racesDB[name]['per']
  475. template['endu'] = racesDB[name]['endu']
  476. template['cha'] = racesDB[name]['cha']
  477. template['int'] = racesDB[name]['int']
  478. template['agi'] = racesDB[name]['agi']
  479. template['luc'] = racesDB[name]['luc']
  480. template['cool'] = racesDB[name]['cool']
  481. template['ref'] = racesDB[name]['ref']
  482. def prepareSpells(mud, id, players):
  483. """Player prepares spells, called once per second
  484. """
  485. if len(players[id]['prepareSpell']) == 0:
  486. return
  487. if players[id]['prepareSpellTime'] > 0:
  488. if players[id]['prepareSpellProgress'] >= \
  489. players[id]['prepareSpellTime']:
  490. spellName = players[id]['prepareSpell']
  491. players[id]['preparedSpells'][spellName] = 1
  492. players[id]['spellSlots'][spellName] = 1
  493. players[id]['prepareSpell'] = ""
  494. players[id]['prepareSpellProgress'] = 0
  495. players[id]['prepareSpellTime'] = 0
  496. mud.sendMessage(
  497. id, "You prepared the spell <f220>" +
  498. spellName + "<r>\n\n")
  499. else:
  500. players[id]['prepareSpellProgress'] = \
  501. players[id]['prepareSpellProgress'] + 1
  502. def isWearing(id, players: {}, itemList: []) -> bool:
  503. """Given a list of possible item IDs is the player wearing any of them?
  504. """
  505. if not itemList:
  506. return False
  507. for itemID in itemList:
  508. if not str(itemID).isdigit():
  509. print('isWearing: ' + str(itemID) + ' is not a digit')
  510. continue
  511. itemID = int(itemID)
  512. for locn in wearLocation:
  513. if int(players[id]['clo_'+locn]) == itemID:
  514. return True
  515. if int(players[id]['clo_lhand']) == itemID or \
  516. int(players[id]['clo_rhand']) == itemID:
  517. return True
  518. return False
  519. def playerIsVisible(mud, observerId: int, observers: {},
  520. otherPlayerId: int, others: {}) -> bool:
  521. """Is the other player visible to the observer?
  522. """
  523. observerId = int(observerId)
  524. otherPlayerId = int(otherPlayerId)
  525. if not others[otherPlayerId].get('visibleWhenWearing'):
  526. return True
  527. if others[otherPlayerId].get('visibleWhenWearing'):
  528. if isWearing(observerId, observers,
  529. others[otherPlayerId]['visibleWhenWearing']):
  530. return True
  531. return False
  532. def messageToPlayersInRoom(mud, players, id, msg):
  533. # go through all the players in the game
  534. for (pid, pl) in list(players.items()):
  535. # if player is in the same room and isn't the player
  536. # sending the command
  537. if players[pid]['room'] == players[id]['room'] and \
  538. pid != id:
  539. if playerIsVisible(mud, pid, players, id, players):
  540. mud.sendMessage(pid, msg)