combat.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135
  1. __filename__ = "combat.py"
  2. __author__ = "Bob Mottram"
  3. __credits__ = ["Bob Mottram"]
  4. __license__ = "AGPL3+"
  5. __version__ = "1.0.0"
  6. __maintainer__ = "Bob Mottram"
  7. __email__ = "bob@freedombone.net"
  8. __status__ = "Production"
  9. #!/usr/bin/python
  10. # -*- coding: utf-8 -*-
  11. from functions import log
  12. from functions import playerInventoryWeight
  13. from functions import stowHands
  14. from functions import prepareSpells
  15. from functions import randomDescription
  16. from functions import decreaseAffinityBetweenPlayers
  17. from random import randint
  18. from copy import deepcopy
  19. from environment import getTemperatureAtCoords
  20. from proficiencies import damageProficiency
  21. from proficiencies import defenseProficiency
  22. from proficiencies import weaponProficiency
  23. from traps import playerIsTrapped
  24. import time
  25. defenseClothing = (
  26. 'clo_chest',
  27. 'clo_head',
  28. 'clo_larm',
  29. 'clo_rarm',
  30. 'clo_lleg',
  31. 'clo_rleg',
  32. 'clo_lwrist',
  33. 'clo_rwrist')
  34. def updateTemporaryIncapacitation(mud, players: {}, isNPC: bool) -> None:
  35. """Checks if players are incapacitated by spells and removes them
  36. after the duration has elapsed
  37. """
  38. now = int(time.time())
  39. for p in players:
  40. if players[p]['name'] is None:
  41. continue
  42. if players[p]['frozenStart'] != 0:
  43. if now >= players[p]['frozenStart'] + players[p]['frozenDuration']:
  44. players[p]['frozenStart'] = 0
  45. players[p]['frozenDuration'] = 0
  46. players[p]['frozenDescription'] = ""
  47. if not isNPC:
  48. mud.send_message(
  49. p, "<f220>You find that you can move again.<r>\n\n")
  50. def updateTemporaryHitPoints(mud, players: {}, isNPC: bool) -> None:
  51. """Updates any hit points added for a temporary period
  52. as the result of a spell
  53. """
  54. now = int(time.time())
  55. for p in players:
  56. if players[p]['name'] is None:
  57. continue
  58. if players[p]['tempHitPoints'] == 0:
  59. continue
  60. if players[p]['tempHitPointsStart'] == 0 and \
  61. players[p]['tempHitPointsDuration'] > 0:
  62. players[p]['tempHitPointsStart'] = now
  63. else:
  64. if now > players[p]['tempHitPointsStart'] + \
  65. players[p]['tempHitPointsDuration']:
  66. players[p]['tempHitPoints'] = 0
  67. players[p]['tempHitPointsStart'] = 0
  68. players[p]['tempHitPointsDuration'] = 0
  69. if not isNPC:
  70. mud.send_message(
  71. p, "<f220>Your magical protection expires.<r>\n\n")
  72. def updateTemporaryCharm(mud, players: {}, isNPC: bool) -> None:
  73. """Updates any charm added for a temporary period
  74. as the result of a spell
  75. """
  76. now = int(time.time())
  77. for p in players:
  78. if players[p]['name'] is None:
  79. continue
  80. if players[p]['tempCharm'] == 0:
  81. continue
  82. if players[p]['tempCharmStart'] == 0 and \
  83. players[p]['tempCharmDuration'] > 0:
  84. players[p]['tempCharmStart'] = now
  85. else:
  86. if not players[p].get('tempCharmDuration'):
  87. return
  88. if now > players[p]['tempCharmStart'] + \
  89. players[p]['tempCharmDuration']:
  90. players[p]['tempCharmStart'] = 0
  91. players[p]['tempCharmDuration'] = 0
  92. if players[p]['affinity'].get(players[p]['tempCharmTarget']):
  93. players[p]['affinity'][players[p]['tempCharmTarget']]-=players[p]['tempCharm']
  94. players[p]['tempCharm'] = 0
  95. if not isNPC:
  96. mud.send_message(
  97. p, "<f220>A charm spell wears off.<r>\n\n")
  98. def playersRest(mud, players: {}) -> None:
  99. """Rest restores hit points
  100. """
  101. for p in players:
  102. if players[p]['name'] is not None and players[p]['authenticated'] is not None:
  103. if players[p]['hp'] < players[p]['hpMax'] + \
  104. players[p]['tempHitPoints']:
  105. if randint(0, 100) > 90:
  106. players[p]['hp'] += 1
  107. else:
  108. players[p]['hp'] = players[p]['hpMax'] + \
  109. players[p]['tempHitPoints']
  110. players[p]['restRequired'] = 0
  111. prepareSpells(mud, p, players)
  112. def itemInNPCInventory(npcs, id: int, itemName: str, itemsDB: {}) -> bool:
  113. if len(list(npcs[id]['inv'])) > 0:
  114. itemNameLower = itemName.lower()
  115. for i in list(npcs[id]['inv']):
  116. if itemsDB[int(i)]['name'].lower() == itemNameLower:
  117. return True
  118. return False
  119. def npcUpdateLuck(nid, npcs: {}, items: {}, itemsDB: {}) -> None:
  120. """Calculate the luck of an NPC based on what items they are carrying
  121. """
  122. luck = 0
  123. for i in npcs[nid]['inv']:
  124. luck = luck + itemsDB[int(i)]['mod_luc']
  125. npcs[nid]['luc'] = luck
  126. def npcWieldsWeapon(mud, id: int, nid, npcs: {}, items: {}, itemsDB: {}) -> bool:
  127. """what is the best weapon which the NPC is carrying?
  128. """
  129. itemID = 0
  130. max_protection = 0
  131. max_damage = 0
  132. if int(npcs[nid]['canWield']) != 0:
  133. for i in npcs[nid]['inv']:
  134. if itemsDB[int(i)]['clo_rhand'] > 0:
  135. if itemsDB[int(i)]['mod_str'] > max_damage:
  136. max_damage = itemsDB[int(i)]['mod_str']
  137. itemID = int(i)
  138. putOnArmor = False
  139. if int(npcs[nid]['canWear']) != 0:
  140. for i in npcs[nid]['inv']:
  141. if itemsDB[int(i)]['clo_chest'] > 0:
  142. if itemsDB[int(i)]['mod_endu'] > max_protection:
  143. max_protection = itemsDB[int(i)]['mod_endu']
  144. itemID = int(i)
  145. putOnArmor = True
  146. # search for any weapons on the floor
  147. pickedUpWeapon = False
  148. itemWeaponIndex = 0
  149. if int(npcs[nid]['canWield']) != 0:
  150. itemsInWorldCopy = deepcopy(items)
  151. for (iid, pl) in list(itemsInWorldCopy.items()):
  152. if itemsInWorldCopy[iid]['room'] == npcs[nid]['room']:
  153. if itemsDB[items[iid]['id']]['weight'] > 0:
  154. if itemsDB[items[iid]['id']]['clo_rhand'] > 0:
  155. if itemsDB[items[iid]['id']]['mod_str'] > max_damage:
  156. itemName = itemsDB[items[iid]['id']]['name']
  157. if not itemInNPCInventory(
  158. npcs, nid, itemName, itemsDB):
  159. max_damage = itemsDB[items[iid]
  160. ['id']]['mod_str']
  161. itemID = int(items[iid]['id'])
  162. itemWeaponIndex = iid
  163. pickedUpWeapon = True
  164. # Search for any armor on the floor
  165. pickedUpArmor = False
  166. itemArmorIndex = 0
  167. if int(npcs[nid]['canWear']) != 0:
  168. for (iid, pl) in list(itemsInWorldCopy.items()):
  169. if itemsInWorldCopy[iid]['room'] == npcs[nid]['room']:
  170. if itemsDB[items[iid]['id']]['weight'] > 0:
  171. if itemsDB[items[iid]['id']]['clo_chest'] > 0:
  172. if itemsDB[items[iid]['id']
  173. ]['mod_endu'] > max_protection:
  174. itemName = itemsDB[items[iid]['id']]['name']
  175. if not itemInNPCInventory(
  176. npcs, nid, itemName, itemsDB):
  177. max_protection = itemsDB[items[iid]
  178. ['id']]['mod_endu']
  179. itemID = int(items[iid]['id'])
  180. itemArmorIndex = iid
  181. pickedUpArmor = True
  182. if itemID > 0:
  183. if putOnArmor:
  184. if npcs[nid]['clo_chest'] != itemID:
  185. npcs[nid]['clo_chest'] = itemID
  186. mud.send_message(
  187. id,
  188. '<f220>' +
  189. npcs[nid]['name'] +
  190. '<r> puts on ' +
  191. itemsDB[itemID]['article'] +
  192. ' ' +
  193. itemsDB[itemID]['name'] +
  194. '\n')
  195. return True
  196. return False
  197. if pickedUpArmor:
  198. if npcs[nid]['clo_chest'] != itemID:
  199. npcs[nid]['inv'].append(str(itemID))
  200. npcs[nid]['clo_chest'] = itemID
  201. del items[itemArmorIndex]
  202. mud.send_message(
  203. id,
  204. '<f220>' +
  205. npcs[nid]['name'] +
  206. '<r> picks up and wears ' +
  207. itemsDB[itemID]['article'] +
  208. ' ' +
  209. itemsDB[itemID]['name'] +
  210. '\n')
  211. return True
  212. return False
  213. if npcs[nid]['clo_rhand'] != itemID:
  214. # Transfer weapon to hand
  215. npcs[nid]['clo_rhand'] = itemID
  216. npcs[nid]['clo_lhand'] = 0
  217. if pickedUpWeapon:
  218. npcs[nid]['inv'].append(str(itemID))
  219. del items[itemWeaponIndex]
  220. mud.send_message(
  221. id,
  222. '<f220>' +
  223. npcs[nid]['name'] +
  224. '<r> picks up ' +
  225. itemsDB[itemID]['article'] +
  226. ' ' +
  227. itemsDB[itemID]['name'] +
  228. '\n')
  229. else:
  230. mud.send_message(
  231. id,
  232. '<f220>' +
  233. npcs[nid]['name'] +
  234. '<r> has drawn their ' +
  235. itemsDB[itemID]['name'] +
  236. '\n')
  237. return True
  238. return False
  239. def npcWearsArmor(id: int, npcs: {}, itemsDB: {}) -> None:
  240. """An NPC puts on armor
  241. """
  242. if len(npcs[id]['inv']) == 0:
  243. return
  244. for c in defenseClothing:
  245. itemID = 0
  246. # what is the best defense which the NPC is carrying?
  247. max_defense = 0
  248. for i in npcs[id]['inv']:
  249. if itemsDB[int(i)][c] > 0:
  250. if itemsDB[int(i)]['mod_str'] == 0:
  251. if itemsDB[int(i)]['mod_endu'] > max_defense:
  252. max_defense = itemsDB[int(i)]['mod_endu']
  253. itemID = int(i)
  254. if itemID > 0:
  255. # Wear the armor
  256. npcs[id][c] = itemID
  257. def weaponDamage(id: int, players: {}, itemsDB: {}, weaponType: str, characterClassDB: {}) -> int:
  258. """Calculates the amount of damage which a player can do
  259. with weapons held
  260. """
  261. damage = 0
  262. itemID = players[id]['clo_lhand']
  263. if itemID > 0:
  264. damage = damage + itemsDB[itemID]['mod_str']
  265. itemID = players[id]['clo_rhand']
  266. if itemID > 0:
  267. damage = damage + itemsDB[itemID]['mod_str']
  268. # Extra damage based on proficiencies
  269. if damage > 0:
  270. damageProficiency(id, players, weaponType, characterClassDB)
  271. # Total damage inflicted by weapons
  272. return damage
  273. def raceResistance(id: int, players: {}, racesDB: {}, weaponType: str) -> int:
  274. """How much resistance does the player have to the weapon type
  275. based upon their race
  276. """
  277. resistance = 0
  278. resistParam = 'resist_' + weaponType.lower()
  279. if weaponType.endswith('bow'):
  280. resistParam = 'resist_piercing'
  281. if weaponType.endswith('sling'):
  282. resistParam = 'resist_bludgeoning'
  283. if players[id].get('race'):
  284. race = players[id]['race'].lower()
  285. if racesDB.get(race):
  286. if racesDB[race].get(resistParam):
  287. resistance = racesDB[race][resistParam]
  288. return resistance
  289. def weaponDefense(id: int, players: {}, itemsDB: {}, racesDB: {}, weaponType: str, characterClassDB: {}) -> int:
  290. """How much defense does a player have due to armor worn?
  291. """
  292. defense = raceResistance(id, players, racesDB, weaponType)
  293. for c in defenseClothing:
  294. itemID = int(players[id][c])
  295. if itemID > 0:
  296. defense = defense + int(itemsDB[itemID]['mod_endu'])
  297. if defense > 0:
  298. defenseProficiency(id, players, characterClassDB)
  299. # Total defense by shields or clothing
  300. return defense
  301. def armorAgility(id: int, players: {}, itemsDB: {}) -> int:
  302. """Modify agility based on armor worn
  303. """
  304. agility = 0
  305. for c in defenseClothing:
  306. itemID = int(players[id][c])
  307. if itemID > 0:
  308. agility = agility + int(itemsDB[itemID]['mod_agi'])
  309. # Total agility for clothing
  310. return agility
  311. def canUseWeapon(id: int, players: {}, itemsDB: {}, itemID: int) -> bool:
  312. if itemID == 0:
  313. return True
  314. lockItemID = itemsDB[itemID]['lockedWithItem']
  315. if lockItemID > 0:
  316. itemName = itemsDB[lockItemID]['name']
  317. for i in list(players[id]['inv']):
  318. if itemsDB[int(i)]['name'] == itemName:
  319. return True
  320. return False
  321. return True
  322. def getWeaponHeld(id: int, players: {}, itemsDB: {}) -> (int,str,int):
  323. """Returns the type of weapon held, or fists if none is held and the rounds of fire
  324. """
  325. if players[id]['clo_rhand'] > 0 and players[id]['clo_lhand'] == 0:
  326. # something in right hand
  327. itemID = int(players[id]['clo_rhand'])
  328. if itemsDB[itemID]['mod_str'] > 0:
  329. if len(itemsDB[itemID]['type']) > 0:
  330. return itemID, itemsDB[itemID]['type'], itemsDB[itemID]['rof']
  331. if players[id]['clo_lhand'] > 0 and players[id]['clo_rhand'] == 0:
  332. # something in left hand
  333. itemID = int(players[id]['clo_lhand'])
  334. if itemsDB[itemID]['mod_str'] > 0:
  335. if len(itemsDB[itemID]['type']) > 0:
  336. return itemID, itemsDB[itemID]['type'], itemsDB[itemID]['rof']
  337. if players[id]['clo_lhand'] > 0 and players[id]['clo_rhand'] > 0:
  338. # something in both hands
  339. itemRightID = int(players[id]['clo_rhand'])
  340. itemLeftID = int(players[id]['clo_lhand'])
  341. if randint(0, 1) == 1:
  342. if itemsDB[itemRightID]['mod_str'] > 0:
  343. if len(itemsDB[itemRightID]['type']) > 0:
  344. return itemRightID, itemsDB[itemRightID]['type'], itemsDB[itemRightID]['rof']
  345. if itemsDB[itemLeftID]['mod_str'] > 0:
  346. if len(itemsDB[itemLeftID]['type']) > 0:
  347. return itemLeftID, itemsDB[itemLeftID]['type'], itemsDB[itemLeftID]['rof']
  348. else:
  349. if itemsDB[itemLeftID]['mod_str'] > 0:
  350. if len(itemsDB[itemLeftID]['type']) > 0:
  351. return itemLeftID, itemsDB[itemLeftID]['type'], itemsDB[itemLeftID]['rof']
  352. if itemsDB[itemRightID]['mod_str'] > 0:
  353. if len(itemsDB[itemRightID]['type']) > 0:
  354. return itemRightID, itemsDB[itemRightID]['type'], itemsDB[itemRightID]['rof']
  355. return 0, "fists", 1
  356. def getAttackDescription(weaponType: str) -> (str,str):
  357. """Describes an attack with a given type of weapon. This
  358. Returns both the first person and second person
  359. perspective descriptions
  360. """
  361. weaponType = weaponType.lower()
  362. attackStrings = [
  363. "swing a fist at",
  364. "punch",
  365. "crudely swing a fist at",
  366. "ineptly punch"
  367. ]
  368. attackDescriptionFirst = \
  369. attackStrings[randint(0, len(attackStrings) - 1)]
  370. attackStrings = [
  371. "swung a fist at",
  372. "punched",
  373. "crudely swung a fist at",
  374. "ineptly punched"
  375. ]
  376. attackDescriptionSecond = \
  377. attackStrings[randint(0, len(attackStrings) - 1)]
  378. if weaponType.startswith("acid"):
  379. attackStrings = ["corrode", "spray", "splash"]
  380. attackDescriptionFirst = \
  381. attackStrings[randint(0, len(attackStrings) - 1)]
  382. attackStrings = ["corroded", "sprayed", "splashed"]
  383. attackDescriptionSecond = \
  384. attackStrings[randint(0, len(attackStrings) - 1)]
  385. if weaponType.startswith("bludg"):
  386. attackStrings = [
  387. "deliver a crushing blow on",
  388. "strike at",
  389. "swing at",
  390. "swing clumsily at",
  391. "strike a blow on"
  392. ]
  393. attackDescriptionFirst = \
  394. attackStrings[randint(0, len(attackStrings) - 1)]
  395. attackStrings = [
  396. "delivered a crushing blow on",
  397. "struck at",
  398. "swung at",
  399. "swung clumsily at",
  400. "struck a blow on"
  401. ]
  402. attackDescriptionSecond = \
  403. attackStrings[randint(0, len(attackStrings) - 1)]
  404. if weaponType.startswith("cold"):
  405. attackStrings = ["freeze", "chill"]
  406. attackDescriptionFirst = \
  407. attackStrings[randint(0, len(attackStrings) - 1)]
  408. attackStrings = ["froze", "chilled"]
  409. attackDescriptionSecond = \
  410. attackStrings[randint(0, len(attackStrings) - 1)]
  411. if weaponType.startswith("fire"):
  412. attackStrings = [
  413. "cast a ball a of flame at",
  414. "cast a fireball at",
  415. "cast a burning sphere at"
  416. ]
  417. attackDescriptionFirst = \
  418. attackStrings[randint(0, len(attackStrings) - 1)]
  419. attackStrings = [
  420. "casted a ball a of flame at",
  421. "casted a fireball at",
  422. "casted a burning sphere at"
  423. ]
  424. attackDescriptionSecond = \
  425. attackStrings[randint(0, len(attackStrings) - 1)]
  426. if weaponType.startswith("force"):
  427. attackStrings = ["point at", "wave at"]
  428. attackDescriptionFirst = \
  429. attackStrings[randint(0, len(attackStrings) - 1)]
  430. attackStrings = ["pointed at", "waved at"]
  431. attackDescriptionSecond = \
  432. attackStrings[randint(0, len(attackStrings) - 1)]
  433. if weaponType.startswith("lightning"):
  434. attackStrings = [
  435. "cast a bolt of lightning at",
  436. "cast a lightning bolt at"]
  437. attackDescriptionFirst = \
  438. attackStrings[randint(0, len(attackStrings) - 1)]
  439. attackStrings = [
  440. "casted a bolt of lightning at",
  441. "casted a lightning bolt at"]
  442. attackDescriptionSecond = \
  443. attackStrings[randint(0, len(attackStrings) - 1)]
  444. if weaponType.startswith("necro"):
  445. attackStrings = ["whither", "chill"]
  446. attackDescriptionFirst = \
  447. attackStrings[randint(0, len(attackStrings) - 1)]
  448. attackStrings = ["whithered", "chilled"]
  449. attackDescriptionSecond = \
  450. attackStrings[randint(0, len(attackStrings) - 1)]
  451. if weaponType.startswith("pierc"):
  452. attackStrings = ["stab at", "hack at"]
  453. attackDescriptionFirst = \
  454. attackStrings[randint(0, len(attackStrings) - 1)]
  455. attackStrings = ["stabbed at", "hacked at"]
  456. attackDescriptionSecond = \
  457. attackStrings[randint(0, len(attackStrings) - 1)]
  458. if weaponType.startswith("poison"):
  459. attackStrings = ["poison"]
  460. attackDescriptionFirst = \
  461. attackStrings[randint(0, len(attackStrings) - 1)]
  462. attackStrings = ["poisoned"]
  463. attackDescriptionSecond = \
  464. attackStrings[randint(0, len(attackStrings) - 1)]
  465. if weaponType.startswith("psy"):
  466. attackStrings = ["psychically blast", "psychically deplete"]
  467. attackDescriptionFirst = \
  468. attackStrings[randint(0, len(attackStrings) - 1)]
  469. attackStrings = ["psychically blasted", "psychically depleted"]
  470. attackDescriptionSecond = \
  471. attackStrings[randint(0, len(attackStrings) - 1)]
  472. if weaponType.startswith("radiant"):
  473. attackStrings = ["sear", "scorch"]
  474. attackDescriptionFirst = \
  475. attackStrings[randint(0, len(attackStrings) - 1)]
  476. attackStrings = ["seared", "scorched"]
  477. attackDescriptionSecond = \
  478. attackStrings[randint(0, len(attackStrings) - 1)]
  479. if weaponType.startswith("slash"):
  480. attackStrings = [
  481. "cut at",
  482. "cut savagely into",
  483. "slash at",
  484. "swing at",
  485. "swing clumsily at"
  486. ]
  487. attackDescriptionFirst = \
  488. attackStrings[randint(0, len(attackStrings) - 1)]
  489. attackStrings = [
  490. "cut at",
  491. "cut savagely into",
  492. "slashed at",
  493. "swung at",
  494. "swung clumsily at"]
  495. attackDescriptionSecond = \
  496. attackStrings[randint(0, len(attackStrings) - 1)]
  497. if weaponType.startswith("thunder"):
  498. attackStrings = ["cast a thunderbolt at", "cast a bolt of thunder at"]
  499. attackDescriptionFirst = \
  500. attackStrings[randint(0, len(attackStrings) - 1)]
  501. attackStrings = [
  502. "casted a thunderbolt at",
  503. "casted a bolt of thunder at"]
  504. attackDescriptionSecond = \
  505. attackStrings[randint(0, len(attackStrings) - 1)]
  506. if weaponType.startswith("ranged bow") or \
  507. weaponType.startswith("ranged shortbow") or \
  508. weaponType.startswith("ranged longbow"):
  509. attackStrings = ["fire an arrow at", "release an arrow at"]
  510. attackDescriptionFirst = \
  511. attackStrings[randint(0, len(attackStrings) - 1)]
  512. attackStrings = ["fired an arrow at", "released an arrow at"]
  513. attackDescriptionSecond = \
  514. attackStrings[randint(0, len(attackStrings) - 1)]
  515. if weaponType.startswith("ranged crossbow"):
  516. attackStrings = ["fire a bolt at", "release a bolt at"]
  517. attackDescriptionFirst = \
  518. attackStrings[randint(0, len(attackStrings) - 1)]
  519. attackStrings = ["fired a bolt at", "released a bolt at"]
  520. attackDescriptionSecond = \
  521. attackStrings[randint(0, len(attackStrings) - 1)]
  522. if weaponType.startswith("ranged sling"):
  523. attackStrings = ["sling a rock at"]
  524. attackDescriptionFirst = \
  525. attackStrings[randint(0, len(attackStrings) - 1)]
  526. attackStrings = ["slung a rock at"]
  527. attackDescriptionSecond = \
  528. attackStrings[randint(0, len(attackStrings) - 1)]
  529. if weaponType.startswith("ranged dart"):
  530. attackStrings = ["blow a dart at"]
  531. attackDescriptionFirst = \
  532. attackStrings[randint(0, len(attackStrings) - 1)]
  533. attackStrings = ["blew a dart at"]
  534. attackDescriptionSecond = \
  535. attackStrings[randint(0, len(attackStrings) - 1)]
  536. return attackDescriptionFirst, attackDescriptionSecond
  537. def getTemperatureDifficulty(rm: str, rooms: {}, mapArea: [], clouds: {}) -> int:
  538. """Returns a difficulty factor based on the ambient
  539. temperature
  540. """
  541. temperature = getTemperatureAtCoords(
  542. rooms[rm]['coords'], rooms, mapArea, clouds)
  543. if temperature > 5:
  544. # Things get difficult when hotter
  545. return int(temperature / 4)
  546. # Things get difficult in snow/ice
  547. return -(temperature - 5)
  548. def attackRoll(luck: int) -> bool:
  549. """Did an attack succeed?
  550. """
  551. if luck > 6:
  552. luck = 6
  553. if randint(1 + luck, 10) > 5:
  554. return True
  555. return False
  556. def criticalHit() -> bool:
  557. """Is this a critical hit (extra damage)?
  558. """
  559. if randint(1, 20) == 20:
  560. return True
  561. return False
  562. def calculateDamage(weapons: int, defense: int) -> (int,int,str):
  563. """Returns the amount of damage an attack causes
  564. """
  565. damageDescription = 'damage'
  566. damageValue = weapons
  567. armorClass = defense
  568. if criticalHit():
  569. damageDescription = 'critical damage'
  570. damageValue = damageValue * 2
  571. return damageValue, armorClass, damageDescription
  572. def runFightsBetweenPlayers(
  573. mud,
  574. players: {},
  575. npcs: {},
  576. fights,
  577. fid,
  578. itemsDB: {},
  579. rooms: {},
  580. maxTerrainDifficulty,
  581. mapArea: [],
  582. clouds: {},
  583. racesDB: {},
  584. characterClassDB: {}):
  585. """A fight between two players
  586. """
  587. s1id = fights[fid]['s1id']
  588. s2id = fights[fid]['s2id']
  589. # In the same room?
  590. if players[s1id]['room'] != players[s2id]['room']:
  591. return
  592. # is the player frozen?
  593. if players[s1id]['frozenStart'] > 0 or players[s1id]['canAttack'] == 0:
  594. mud.send_message(
  595. s2id, randomDescription(
  596. players[s1id]['frozenDescription']) + '\n')
  597. return
  598. if playerIsTrapped(s1id,players,rooms):
  599. return
  600. currRoom = players[s1id]['room']
  601. weightDifficulty = \
  602. int(playerInventoryWeight(s1id, players, itemsDB) / 20)
  603. temperatureDifficulty = getTemperatureDifficulty(
  604. currRoom, rooms, mapArea, clouds)
  605. terrainDifficulty = \
  606. rooms[players[s1id]['room']]['terrainDifficulty'] * \
  607. 10 / maxTerrainDifficulty
  608. # Agility of player
  609. if int(time.time()) < \
  610. players[s1id]['lastCombatAction'] + \
  611. 10 - players[s1id]['agi'] - \
  612. armorAgility(s1id, players, itemsDB) + \
  613. terrainDifficulty + temperatureDifficulty + weightDifficulty:
  614. return
  615. if players[s2id]['isAttackable'] == 1:
  616. players[s1id]['isInCombat'] = 1
  617. players[s2id]['isInCombat'] = 1
  618. weaponID, weaponType, roundsOfFire = \
  619. getWeaponHeld(s1id, players, itemsDB)
  620. if not canUseWeapon(s1id, players, itemsDB, weaponID):
  621. lockItemID = itemsDB[weaponID]['lockedWithItem']
  622. mud.send_message(
  623. s1id,'You take aim, but find you have no ' +
  624. itemsDB[lockItemID]['name'].lower() + '.\n')
  625. mud.send_message(
  626. s2id,'<f32>' +
  627. players[s1id]['name'] +
  628. '<r> takes aim, but finds they have no ' +
  629. itemsDB[lockItemID]['name'].lower() + '.\n')
  630. stowHands(s1id, players, itemsDB, mud)
  631. mud.send_message(
  632. s2id,'<f32>' +
  633. players[s1id]['name'] +
  634. '<r> stows ' +
  635. itemsDB[itemID]['article'] +
  636. ' <b234>' +
  637. itemsDB[itemID]['name'] + '\n\n')
  638. players[s1id]['lastCombatAction'] = int(time.time())
  639. return
  640. # Do damage to the PC here
  641. if attackRoll(
  642. players[s1id]['luc'] +
  643. weaponProficiency(
  644. s1id,
  645. players,
  646. weaponType,
  647. characterClassDB)):
  648. damageValue, armorClass, damageDescription = \
  649. calculateDamage(weaponDamage(s1id, players, itemsDB, weaponType, characterClassDB), \
  650. weaponDefense(s2id, players, itemsDB, racesDB, weaponType, characterClassDB))
  651. if roundsOfFire < 1:
  652. roundsOfFire = 1
  653. attackDescriptionFirst, attackDescriptionSecond = \
  654. getAttackDescription(weaponType)
  655. if armorClass <= damageValue:
  656. if players[s1id]['hp'] > 0:
  657. modifierStr = ''
  658. for firingRound in range(roundsOfFire):
  659. modifier = randint(
  660. 0, 10) + (damageValue * roundsOfFire) - armorClass
  661. damagePoints = players[s1id]['str'] + modifier
  662. if damagePoints < 0:
  663. damagePoints = 0
  664. players[s2id]['hp'] = \
  665. players[s2id]['hp'] - damagePoints
  666. if len(modifierStr) == 0:
  667. modifierStr = modifierStr + str(damagePoints)
  668. else:
  669. modifierStr = \
  670. modifierStr + \
  671. ' + ' + str(damagePoints)
  672. decreaseAffinityBetweenPlayers(
  673. players, s2id, players, s1id)
  674. decreaseAffinityBetweenPlayers(
  675. players, s1id, players, s2id)
  676. mud.send_message(
  677. s1id,'You ' + \
  678. attackDescriptionFirst + \
  679. ' <f32><u>' + \
  680. players[s2id]['name'] + \
  681. '<r> for <f15><b2> * ' + \
  682. modifierStr + \
  683. ' *<r> points of ' + \
  684. damageDescription + '.\n')
  685. mud.send_message(
  686. s2id,'<f32>' + \
  687. players[s1id]['name'] + \
  688. '<r> has ' + \
  689. attackDescriptionSecond + \
  690. ' you for <f15><b88> * ' + \
  691. modifierStr + \
  692. ' *<r> points of ' + \
  693. damageDescription + '.\n')
  694. else:
  695. if players[s1id]['hp'] > 0:
  696. # Attack deflected by armor
  697. mud.send_message(
  698. s1id,'You ' + \
  699. attackDescriptionFirst + \
  700. ' <f32><u>' + \
  701. players[s2id]['name'] + \
  702. '<r> but their armor deflects it.\n')
  703. mud.send_message(
  704. s2id,'<f32>' + \
  705. players[s1id]['name'] + \
  706. '<r> has ' + \
  707. attackDescriptionSecond + \
  708. ' you but it is deflected by your armor.\n')
  709. else:
  710. players[s1id]['lastCombatAction'] = int(time.time())
  711. mud.send_message(
  712. s1id,'You miss trying to hit <f32><u>' + \
  713. players[s2id]['name'] + '\n')
  714. mud.send_message(
  715. s2id,'<f32><u>' + \
  716. players[s1id]['name'] + \
  717. '<r> missed while trying to hit you!\n')
  718. players[s1id]['lastCombatAction'] = int(time.time())
  719. else:
  720. mud.send_message(
  721. s1id, \
  722. '<f225>Suddenly you stop. It wouldn`t be a good idea to attack <f32>' + \
  723. players[s2id]['name'] + \
  724. ' at this time.\n')
  725. fightsCopy = deepcopy(fights)
  726. for (fight, pl) in fightsCopy.items():
  727. if fightsCopy[fight]['s1id'] == s1id and fightsCopy[fight]['s2id'] == s2id:
  728. del fights[fight]
  729. def runFightsBetweenPlayerAndNPC(
  730. mud,
  731. players: {},
  732. npcs: {},
  733. fights,
  734. fid,
  735. itemsDB: {},
  736. rooms: {},
  737. maxTerrainDifficulty,
  738. mapArea: [],
  739. clouds: {},
  740. racesDB: {},
  741. characterClassDB: {}):
  742. """Fight between a player and an NPC
  743. """
  744. s1id = fights[fid]['s1id']
  745. s2id = fights[fid]['s2id']
  746. # In the same room?
  747. if players[s1id]['room'] != npcs[s2id]['room']:
  748. return
  749. # is the player frozen?
  750. if players[s1id]['frozenStart'] > 0 or players[s1id]['canAttack'] == 0:
  751. mud.send_message(
  752. s2id, randomDescription(
  753. players[s1id]['frozenDescription']) + '\n')
  754. return
  755. if playerIsTrapped(s1id,players,rooms):
  756. return
  757. currRoom = players[s1id]['room']
  758. weightDifficulty = int(playerInventoryWeight(s1id, players, itemsDB) / 20)
  759. temperatureDifficulty = getTemperatureDifficulty(
  760. currRoom, rooms, mapArea, clouds)
  761. terrainDifficulty = rooms[players[s1id]['room']
  762. ]['terrainDifficulty'] * 10 / maxTerrainDifficulty
  763. # Agility of player
  764. if int(time.time()) < players[s1id]['lastCombatAction'] + 10 - players[s1id]['agi'] - armorAgility(
  765. s1id, players, itemsDB) + terrainDifficulty + temperatureDifficulty + weightDifficulty:
  766. return
  767. if npcs[s2id]['isAttackable'] == 1:
  768. players[s1id]['isInCombat'] = 1
  769. npcs[s2id]['isInCombat'] = 1
  770. weaponID, weaponType, roundsOfFire = getWeaponHeld(
  771. s1id, players, itemsDB)
  772. if not canUseWeapon(s1id, players, itemsDB, weaponID):
  773. lockItemID = itemsDB[weaponID]['lockedWithItem']
  774. mud.send_message(
  775. s1id,
  776. 'You take aim, but find you have no ' +
  777. itemsDB[lockItemID]['name'].lower() +
  778. '.\n')
  779. stowHands(s1id, players, itemsDB, mud)
  780. players[s1id]['lastCombatAction'] = int(time.time())
  781. return
  782. # Do damage to the NPC here
  783. if attackRoll(
  784. players[s1id]['luc'] +
  785. weaponProficiency(
  786. s1id,
  787. players,
  788. weaponType,
  789. characterClassDB)):
  790. damageValue, armorClass, damageDescription = calculateDamage(
  791. weaponDamage(
  792. s1id, players, itemsDB, weaponType, characterClassDB), weaponDefense(
  793. s2id, npcs, itemsDB, racesDB, weaponType, characterClassDB))
  794. npcWearsArmor(s2id, npcs, itemsDB)
  795. if roundsOfFire < 1:
  796. roundsOfFire = 1
  797. attackDescriptionFirst, attackDescriptionSecond = getAttackDescription(
  798. weaponType)
  799. if armorClass <= damageValue:
  800. if players[s1id]['hp'] > 0:
  801. modifierStr = ''
  802. for firingRound in range(roundsOfFire):
  803. modifier = randint(0, 10) + damageValue - armorClass
  804. damagePoints = players[s1id]['str'] + modifier
  805. if damagePoints < 0:
  806. damagePoints = 0
  807. npcs[s2id]['hp'] = npcs[s2id]['hp'] - damagePoints
  808. if len(modifierStr) == 0:
  809. modifierStr = modifierStr + str(damagePoints)
  810. else:
  811. modifierStr = modifierStr + \
  812. ' + ' + str(damagePoints)
  813. decreaseAffinityBetweenPlayers(npcs, s2id, players, s1id)
  814. decreaseAffinityBetweenPlayers(players, s1id, npcs, s2id)
  815. mud.send_message(
  816. s1id,
  817. 'You ' +
  818. attackDescriptionFirst +
  819. ' <f220>' +
  820. npcs[s2id]['name'] +
  821. '<r> for <b2><f15> * ' +
  822. modifierStr +
  823. ' * <r> points of ' +
  824. damageDescription +
  825. '\n')
  826. else:
  827. if players[s1id]['hp'] > 0:
  828. # Attack deflected by armor
  829. mud.send_message(
  830. s1id,
  831. 'You ' +
  832. attackDescriptionFirst +
  833. ' <f32><u>' +
  834. npcs[s2id]['name'] +
  835. '<r> but their armor deflects it.\n')
  836. else:
  837. players[s1id]['lastCombatAction'] = int(time.time())
  838. mud.send_message(
  839. s1id,
  840. 'You miss <f220>' +
  841. npcs[s2id]['name'] +
  842. '<r> completely!\n')
  843. players[s1id]['lastCombatAction'] = int(time.time())
  844. else:
  845. mud.send_message(
  846. s1id,
  847. '<f225>Suddenly you stop. It wouldn`t be a good idea to attack <u><f21>' +
  848. npcs[s2id]['name'] +
  849. '<r> at this time.\n')
  850. fightsCopy = deepcopy(fights)
  851. for (fight, pl) in fightsCopy.items():
  852. if fightsCopy[fight]['s1id'] == s1id and fightsCopy[fight]['s2id'] == s2id:
  853. del fights[fight]
  854. def runFightsBetweenNPCAndPlayer(
  855. mud,
  856. players: {},
  857. npcs: {},
  858. fights,
  859. fid,
  860. items: {},
  861. itemsDB: {},
  862. rooms: {},
  863. maxTerrainDifficulty,
  864. mapArea,
  865. clouds: {},
  866. racesDB: {},
  867. characterClassDB: {}):
  868. """Fight between NPC and player
  869. """
  870. s1id = fights[fid]['s1id']
  871. s2id = fights[fid]['s2id']
  872. # In the same room?
  873. if npcs[s1id]['room'] != players[s2id]['room']:
  874. return
  875. # is the player frozen?
  876. if npcs[s1id]['frozenStart'] > 0:
  877. mud.send_message(
  878. s2id,
  879. '<f220>' +
  880. npcs[s1id]['name'] +
  881. "<r> tries to attack but can't move\n")
  882. return
  883. currRoom = npcs[s1id]['room']
  884. weightDifficulty = int(playerInventoryWeight(s1id, npcs, itemsDB) / 20)
  885. temperatureDifficulty = getTemperatureDifficulty(
  886. currRoom, rooms, mapArea, clouds)
  887. terrainDifficulty = rooms[players[s2id]['room']
  888. ]['terrainDifficulty'] * 10 / maxTerrainDifficulty
  889. # Agility of NPC
  890. if int(time.time()) < npcs[s1id]['lastCombatAction'] + 10 - npcs[s1id]['agi'] - armorAgility(
  891. s1id, npcs, itemsDB) + terrainDifficulty + temperatureDifficulty + weightDifficulty:
  892. return
  893. npcs[s1id]['isInCombat'] = 1
  894. players[s2id]['isInCombat'] = 1
  895. npcUpdateLuck(s1id, npcs, items, itemsDB)
  896. if npcWieldsWeapon(mud, s2id, s1id, npcs, items, itemsDB):
  897. return
  898. weaponID, weaponType, roundsOfFire = getWeaponHeld(s1id, npcs, itemsDB)
  899. # Do the damage to PC here
  900. if attackRoll(npcs[s1id]['luc'] +
  901. weaponProficiency(s1id, npcs, weaponType, characterClassDB)):
  902. damageValue, armorClass, damageDescription = calculateDamage(
  903. weaponDamage(
  904. s1id, npcs, itemsDB, weaponType, characterClassDB), weaponDefense(
  905. s2id, players, itemsDB, racesDB, weaponType, characterClassDB))
  906. if roundsOfFire < 1:
  907. roundsOfFire = 1
  908. attackDescriptionFirst, attackDescriptionSecond = getAttackDescription(
  909. weaponType)
  910. if armorClass <= damageValue:
  911. if npcs[s1id]['hp'] > 0:
  912. modifierStr = ''
  913. for firingRound in range(roundsOfFire):
  914. modifier = randint(
  915. 0, 10) + damageValue - armorClass - npcs[s1id]['tempHitPoints']
  916. damagePoints = npcs[s1id]['str'] + modifier
  917. if damagePoints < 0:
  918. damagePoints = 0
  919. players[s2id]['hp'] = players[s2id]['hp'] - damagePoints
  920. if len(modifierStr) == 0:
  921. modifierStr = modifierStr + str(damagePoints)
  922. else:
  923. modifierStr = modifierStr + ' + ' + str(damagePoints)
  924. decreaseAffinityBetweenPlayers(npcs, s1id, players, s2id)
  925. decreaseAffinityBetweenPlayers(players, s2id, npcs, s1id)
  926. mud.send_message(
  927. s2id,
  928. '<f220>' +
  929. npcs[s1id]['name'] +
  930. '<r> has ' +
  931. attackDescriptionSecond +
  932. ' you for <f15><b88> * ' +
  933. modifierStr +
  934. ' * <r> points of ' +
  935. damageDescription +
  936. '.\n')
  937. else:
  938. mud.send_message(
  939. s2id,
  940. '<f220>' +
  941. npcs[s1id]['name'] +
  942. '<r> has ' +
  943. attackDescriptionSecond +
  944. ' you but it is deflected by your armor.\n')
  945. else:
  946. npcs[s1id]['lastCombatAction'] = int(time.time())
  947. mud.send_message(
  948. s2id,
  949. '<f220>' +
  950. npcs[s1id]['name'] +
  951. '<r> has missed you completely!\n')
  952. npcs[s1id]['lastCombatAction'] = int(time.time())
  953. def runFights(
  954. mud,
  955. players: {},
  956. npcs: {},
  957. fights,
  958. items: {},
  959. itemsDB: {},
  960. rooms: {},
  961. maxTerrainDifficulty,
  962. mapArea: [],
  963. clouds: {},
  964. racesDB: {},
  965. characterClassDB: {}):
  966. """Handles fights
  967. """
  968. for (fid, pl) in list(fights.items()):
  969. # PC -> PC
  970. if fights[fid]['s1type'] == 'pc' and fights[fid]['s2type'] == 'pc':
  971. runFightsBetweenPlayers(
  972. mud,
  973. players,
  974. npcs,
  975. fights,
  976. fid,
  977. itemsDB,
  978. rooms,
  979. maxTerrainDifficulty,
  980. mapArea,
  981. clouds,
  982. racesDB,
  983. characterClassDB)
  984. # PC -> NPC
  985. elif fights[fid]['s1type'] == 'pc' and fights[fid]['s2type'] == 'npc':
  986. runFightsBetweenPlayerAndNPC(
  987. mud,
  988. players,
  989. npcs,
  990. fights,
  991. fid,
  992. itemsDB,
  993. rooms,
  994. maxTerrainDifficulty,
  995. mapArea,
  996. clouds,
  997. racesDB,
  998. characterClassDB)
  999. # NPC -> PC
  1000. elif fights[fid]['s1type'] == 'npc' and fights[fid]['s2type'] == 'pc':
  1001. runFightsBetweenNPCAndPlayer(
  1002. mud,
  1003. players,
  1004. npcs,
  1005. fights,
  1006. fid,
  1007. items,
  1008. itemsDB,
  1009. rooms,
  1010. maxTerrainDifficulty,
  1011. mapArea,
  1012. clouds,
  1013. racesDB,
  1014. characterClassDB)
  1015. # NPC -> NPC
  1016. elif fights[fid]['s1type'] == 'npc' and fights[fid]['s2type'] == 'npc':
  1017. test = 1