combat.py 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  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. attackDescriptionFirst = attackStrings[randint(0, len(attackStrings) - 1)]
  368. attackStrings = [
  369. "swung a fist at",
  370. "punched",
  371. "crudely swung a fist at",
  372. "ineptly punched"]
  373. attackDescriptionSecond = attackStrings[randint(0, len(attackStrings) - 1)]
  374. if weaponType.startswith("acid"):
  375. attackStrings = ["corrode", "spray", "splash"]
  376. attackDescriptionFirst = attackStrings[randint(
  377. 0, len(attackStrings) - 1)]
  378. attackStrings = ["corroded", "sprayed", "splashed"]
  379. attackDescriptionSecond = attackStrings[randint(
  380. 0, len(attackStrings) - 1)]
  381. if weaponType.startswith("bludg"):
  382. attackStrings = [
  383. "deliver a crushing blow on",
  384. "strike at",
  385. "swing at",
  386. "swing clumsily at",
  387. "strike a blow on"]
  388. attackDescriptionFirst = attackStrings[randint(
  389. 0, len(attackStrings) - 1)]
  390. attackStrings = [
  391. "delivered a crushing blow on",
  392. "struck at",
  393. "swung at",
  394. "swung clumsily at",
  395. "struck a blow on"]
  396. attackDescriptionSecond = attackStrings[randint(
  397. 0, len(attackStrings) - 1)]
  398. if weaponType.startswith("cold"):
  399. attackStrings = ["freeze", "chill"]
  400. attackDescriptionFirst = attackStrings[randint(
  401. 0, len(attackStrings) - 1)]
  402. attackStrings = ["froze", "chilled"]
  403. attackDescriptionSecond = attackStrings[randint(
  404. 0, len(attackStrings) - 1)]
  405. if weaponType.startswith("fire"):
  406. attackStrings = [
  407. "cast a ball a of flame at",
  408. "cast a fireball at",
  409. "cast a burning sphere at"]
  410. attackDescriptionFirst = attackStrings[randint(
  411. 0, len(attackStrings) - 1)]
  412. attackStrings = [
  413. "casted a ball a of flame at",
  414. "casted a fireball at",
  415. "casted a burning sphere at"]
  416. attackDescriptionSecond = attackStrings[randint(
  417. 0, len(attackStrings) - 1)]
  418. if weaponType.startswith("force"):
  419. attackStrings = ["point at", "wave at"]
  420. attackDescriptionFirst = attackStrings[randint(
  421. 0, len(attackStrings) - 1)]
  422. attackStrings = ["pointed at", "waved at"]
  423. attackDescriptionSecond = attackStrings[randint(
  424. 0, len(attackStrings) - 1)]
  425. if weaponType.startswith("lightning"):
  426. attackStrings = [
  427. "cast a bolt of lightning at",
  428. "cast a lightning bolt at"]
  429. attackDescriptionFirst = attackStrings[randint(
  430. 0, len(attackStrings) - 1)]
  431. attackStrings = [
  432. "casted a bolt of lightning at",
  433. "casted a lightning bolt at"]
  434. attackDescriptionSecond = attackStrings[randint(
  435. 0, len(attackStrings) - 1)]
  436. if weaponType.startswith("necro"):
  437. attackStrings = ["whither", "chill"]
  438. attackDescriptionFirst = attackStrings[randint(
  439. 0, len(attackStrings) - 1)]
  440. attackStrings = ["whithered", "chilled"]
  441. attackDescriptionSecond = attackStrings[randint(
  442. 0, len(attackStrings) - 1)]
  443. if weaponType.startswith("pierc"):
  444. attackStrings = ["stab at", "hack at"]
  445. attackDescriptionFirst = attackStrings[randint(
  446. 0, len(attackStrings) - 1)]
  447. attackStrings = ["stabbed at", "hacked at"]
  448. attackDescriptionSecond = attackStrings[randint(
  449. 0, len(attackStrings) - 1)]
  450. if weaponType.startswith("poison"):
  451. attackStrings = ["poison"]
  452. attackDescriptionFirst = attackStrings[randint(
  453. 0, len(attackStrings) - 1)]
  454. attackStrings = ["poisoned"]
  455. attackDescriptionSecond = attackStrings[randint(
  456. 0, len(attackStrings) - 1)]
  457. if weaponType.startswith("psy"):
  458. attackStrings = ["psychically blast", "psychically deplete"]
  459. attackDescriptionFirst = attackStrings[randint(
  460. 0, len(attackStrings) - 1)]
  461. attackStrings = ["psychically blasted", "psychically depleted"]
  462. attackDescriptionSecond = attackStrings[randint(
  463. 0, len(attackStrings) - 1)]
  464. if weaponType.startswith("radiant"):
  465. attackStrings = ["sear", "scorch"]
  466. attackDescriptionFirst = attackStrings[randint(
  467. 0, len(attackStrings) - 1)]
  468. attackStrings = ["seared", "scorched"]
  469. attackDescriptionSecond = attackStrings[randint(
  470. 0, len(attackStrings) - 1)]
  471. if weaponType.startswith("slash"):
  472. attackStrings = [
  473. "cut at",
  474. "cut savagely into",
  475. "slash at",
  476. "swing at",
  477. "swing clumsily at"]
  478. attackDescriptionFirst = attackStrings[randint(
  479. 0, len(attackStrings) - 1)]
  480. attackStrings = [
  481. "cut at",
  482. "cut savagely into",
  483. "slashed at",
  484. "swung at",
  485. "swung clumsily at"]
  486. attackDescriptionSecond = attackStrings[randint(
  487. 0, len(attackStrings) - 1)]
  488. if weaponType.startswith("thunder"):
  489. attackStrings = ["cast a thunderbolt at", "cast a bolt of thunder at"]
  490. attackDescriptionFirst = attackStrings[randint(
  491. 0, len(attackStrings) - 1)]
  492. attackStrings = [
  493. "casted a thunderbolt at",
  494. "casted a bolt of thunder at"]
  495. attackDescriptionSecond = attackStrings[randint(
  496. 0, len(attackStrings) - 1)]
  497. if weaponType.startswith("ranged bow") or \
  498. weaponType.startswith("ranged shortbow") or \
  499. weaponType.startswith("ranged longbow"):
  500. attackStrings = ["fire an arrow at", "release an arrow at"]
  501. attackDescriptionFirst = attackStrings[randint(
  502. 0, len(attackStrings) - 1)]
  503. attackStrings = ["fired an arrow at", "released an arrow at"]
  504. attackDescriptionSecond = attackStrings[randint(
  505. 0, len(attackStrings) - 1)]
  506. if weaponType.startswith("ranged crossbow"):
  507. attackStrings = ["fire a bolt at", "release a bolt at"]
  508. attackDescriptionFirst = attackStrings[randint(
  509. 0, len(attackStrings) - 1)]
  510. attackStrings = ["fired a bolt at", "released a bolt at"]
  511. attackDescriptionSecond = attackStrings[randint(
  512. 0, len(attackStrings) - 1)]
  513. if weaponType.startswith("ranged sling"):
  514. attackStrings = ["sling a rock at"]
  515. attackDescriptionFirst = attackStrings[randint(
  516. 0, len(attackStrings) - 1)]
  517. attackStrings = ["slung a rock at"]
  518. attackDescriptionSecond = attackStrings[randint(
  519. 0, len(attackStrings) - 1)]
  520. if weaponType.startswith("ranged dart"):
  521. attackStrings = ["blow a dart at"]
  522. attackDescriptionFirst = attackStrings[randint(
  523. 0, len(attackStrings) - 1)]
  524. attackStrings = ["blew a dart at"]
  525. attackDescriptionSecond = attackStrings[randint(
  526. 0, len(attackStrings) - 1)]
  527. return attackDescriptionFirst, attackDescriptionSecond
  528. def getTemperatureDifficulty(rm: str, rooms: {}, mapArea: [], clouds: {}) -> int:
  529. """Returns a difficulty factor based on the ambient
  530. temperature
  531. """
  532. temperature = getTemperatureAtCoords(
  533. rooms[rm]['coords'], rooms, mapArea, clouds)
  534. if temperature > 5:
  535. # Things get difficult when hotter
  536. return int(temperature / 4)
  537. # Things get difficult in snow/ice
  538. return -(temperature - 5)
  539. def attackRoll(luck: int) -> bool:
  540. """Did an attack succeed?
  541. """
  542. if luck > 6:
  543. luck = 6
  544. if randint(1 + luck, 10) > 5:
  545. return True
  546. return False
  547. def criticalHit() -> bool:
  548. """Is this a critical hit (extra damage)?
  549. """
  550. if randint(1, 20) == 20:
  551. return True
  552. return False
  553. def calculateDamage(weapons: int, defense: int) -> (int,int,str):
  554. """Returns the amount of damage an attack causes
  555. """
  556. damageDescription = 'damage'
  557. damageValue = weapons
  558. armorClass = defense
  559. if criticalHit():
  560. damageDescription = 'critical damage'
  561. damageValue = damageValue * 2
  562. return damageValue, armorClass, damageDescription
  563. def runFightsBetweenPlayers(
  564. mud,
  565. players: {},
  566. npcs: {},
  567. fights,
  568. fid,
  569. itemsDB: {},
  570. rooms: {},
  571. maxTerrainDifficulty,
  572. mapArea: [],
  573. clouds: {},
  574. racesDB: {},
  575. characterClassDB: {}):
  576. """A fight between two players
  577. """
  578. s1id = fights[fid]['s1id']
  579. s2id = fights[fid]['s2id']
  580. # In the same room?
  581. if players[s1id]['room'] != players[s2id]['room']:
  582. return
  583. # is the player frozen?
  584. if players[s1id]['frozenStart'] > 0 or players[s1id]['canAttack'] == 0:
  585. mud.send_message(
  586. s2id, randomDescription(
  587. players[s1id]['frozenDescription']) + '\n')
  588. return
  589. if playerIsTrapped(s1id,players,rooms):
  590. return
  591. currRoom = players[s1id]['room']
  592. weightDifficulty = int(playerInventoryWeight(s1id, players, itemsDB) / 20)
  593. temperatureDifficulty = getTemperatureDifficulty(
  594. currRoom, rooms, mapArea, clouds)
  595. terrainDifficulty = rooms[players[s1id]['room']
  596. ]['terrainDifficulty'] * 10 / maxTerrainDifficulty
  597. # Agility of player
  598. if int(time.time()) < players[s1id]['lastCombatAction'] + 10 - players[s1id]['agi'] - armorAgility(
  599. s1id, players, itemsDB) + terrainDifficulty + temperatureDifficulty + weightDifficulty:
  600. return
  601. if players[s2id]['isAttackable'] == 1:
  602. players[s1id]['isInCombat'] = 1
  603. players[s2id]['isInCombat'] = 1
  604. weaponID, weaponType, roundsOfFire = getWeaponHeld(
  605. s1id, players, itemsDB)
  606. if not canUseWeapon(s1id, players, itemsDB, weaponID):
  607. lockItemID = itemsDB[weaponID]['lockedWithItem']
  608. mud.send_message(
  609. s1id,
  610. 'You take aim, but find you have no ' +
  611. itemsDB[lockItemID]['name'].lower() +
  612. '.\n')
  613. mud.send_message(
  614. s2id,
  615. '<f32>' +
  616. players[s1id]['name'] +
  617. '<r> takes aim, but finds they have no ' +
  618. itemsDB[lockItemID]['name'].lower() +
  619. '.\n')
  620. stowHands(s1id, players, itemsDB, mud)
  621. mud.send_message(
  622. s2id,
  623. '<f32>' +
  624. players[s1id]['name'] +
  625. '<r> stows ' +
  626. itemsDB[itemID]['article'] +
  627. ' <b234>' +
  628. itemsDB[itemID]['name'] +
  629. '\n\n')
  630. players[s1id]['lastCombatAction'] = int(time.time())
  631. return
  632. # Do damage to the PC here
  633. if attackRoll(
  634. players[s1id]['luc'] +
  635. weaponProficiency(
  636. s1id,
  637. players,
  638. weaponType,
  639. characterClassDB)):
  640. damageValue, armorClass, damageDescription = calculateDamage(
  641. weaponDamage(
  642. s1id, players, itemsDB, weaponType, characterClassDB), weaponDefense(
  643. s2id, players, itemsDB, racesDB, weaponType, characterClassDB))
  644. if roundsOfFire < 1:
  645. roundsOfFire = 1
  646. attackDescriptionFirst, attackDescriptionSecond = getAttackDescription(
  647. weaponType)
  648. if armorClass <= damageValue:
  649. if players[s1id]['hp'] > 0:
  650. modifierStr = ''
  651. for firingRound in range(roundsOfFire):
  652. modifier = randint(
  653. 0, 10) + (damageValue * roundsOfFire) - armorClass
  654. damagePoints = players[s1id]['str'] + modifier
  655. if damagePoints < 0:
  656. damagePoints = 0
  657. players[s2id]['hp'] = players[s2id]['hp'] - \
  658. damagePoints
  659. if len(modifierStr) == 0:
  660. modifierStr = modifierStr + str(damagePoints)
  661. else:
  662. modifierStr = modifierStr + \
  663. ' + ' + str(damagePoints)
  664. decreaseAffinityBetweenPlayers(
  665. players, s2id, players, s1id)
  666. decreaseAffinityBetweenPlayers(
  667. players, s1id, players, s2id)
  668. mud.send_message(
  669. s1id,
  670. 'You ' +
  671. attackDescriptionFirst +
  672. ' <f32><u>' +
  673. players[s2id]['name'] +
  674. '<r> for <f15><b2> * ' +
  675. modifierStr +
  676. ' *<r> points of ' +
  677. damageDescription +
  678. '.\n')
  679. mud.send_message(
  680. s2id,
  681. '<f32>' +
  682. players[s1id]['name'] +
  683. '<r> has ' +
  684. attackDescriptionSecond +
  685. ' you for <f15><b88> * ' +
  686. modifierStr +
  687. ' *<r> points of ' +
  688. damageDescription +
  689. '.\n')
  690. else:
  691. if players[s1id]['hp'] > 0:
  692. # Attack deflected by armor
  693. mud.send_message(
  694. s1id,
  695. 'You ' +
  696. attackDescriptionFirst +
  697. ' <f32><u>' +
  698. players[s2id]['name'] +
  699. '<r> but their armor deflects it.\n')
  700. mud.send_message(
  701. s2id,
  702. '<f32>' +
  703. players[s1id]['name'] +
  704. '<r> has ' +
  705. attackDescriptionSecond +
  706. ' you but it is deflected by your armor.\n')
  707. else:
  708. players[s1id]['lastCombatAction'] = int(time.time())
  709. mud.send_message(
  710. s1id,
  711. 'You miss trying to hit <f32><u>' +
  712. players[s2id]['name'] +
  713. '\n')
  714. mud.send_message(
  715. s2id,
  716. '<f32><u>' +
  717. players[s1id]['name'] +
  718. '<r> missed while trying to hit you!\n')
  719. players[s1id]['lastCombatAction'] = int(time.time())
  720. else:
  721. mud.send_message(
  722. s1id,
  723. '<f225>Suddenly you stop. It wouldn`t be a good idea to attack <f32>' +
  724. players[s2id]['name'] +
  725. ' at this time.\n')
  726. fightsCopy = deepcopy(fights)
  727. for (fight, pl) in fightsCopy.items():
  728. if fightsCopy[fight]['s1id'] == s1id and fightsCopy[fight]['s2id'] == s2id:
  729. del fights[fight]
  730. def runFightsBetweenPlayerAndNPC(
  731. mud,
  732. players: {},
  733. npcs: {},
  734. fights,
  735. fid,
  736. itemsDB: {},
  737. rooms: {},
  738. maxTerrainDifficulty,
  739. mapArea: [],
  740. clouds: {},
  741. racesDB: {},
  742. characterClassDB: {}):
  743. """Fight between a player and an NPC
  744. """
  745. s1id = fights[fid]['s1id']
  746. s2id = fights[fid]['s2id']
  747. # In the same room?
  748. if players[s1id]['room'] != npcs[s2id]['room']:
  749. return
  750. # is the player frozen?
  751. if players[s1id]['frozenStart'] > 0 or players[s1id]['canAttack'] == 0:
  752. mud.send_message(
  753. s2id, randomDescription(
  754. players[s1id]['frozenDescription']) + '\n')
  755. return
  756. if playerIsTrapped(s1id,players,rooms):
  757. return
  758. currRoom = players[s1id]['room']
  759. weightDifficulty = int(playerInventoryWeight(s1id, players, itemsDB) / 20)
  760. temperatureDifficulty = getTemperatureDifficulty(
  761. currRoom, rooms, mapArea, clouds)
  762. terrainDifficulty = rooms[players[s1id]['room']
  763. ]['terrainDifficulty'] * 10 / maxTerrainDifficulty
  764. # Agility of player
  765. if int(time.time()) < players[s1id]['lastCombatAction'] + 10 - players[s1id]['agi'] - armorAgility(
  766. s1id, players, itemsDB) + terrainDifficulty + temperatureDifficulty + weightDifficulty:
  767. return
  768. if npcs[s2id]['isAttackable'] == 1:
  769. players[s1id]['isInCombat'] = 1
  770. npcs[s2id]['isInCombat'] = 1
  771. weaponID, weaponType, roundsOfFire = getWeaponHeld(
  772. s1id, players, itemsDB)
  773. if not canUseWeapon(s1id, players, itemsDB, weaponID):
  774. lockItemID = itemsDB[weaponID]['lockedWithItem']
  775. mud.send_message(
  776. s1id,
  777. 'You take aim, but find you have no ' +
  778. itemsDB[lockItemID]['name'].lower() +
  779. '.\n')
  780. stowHands(s1id, players, itemsDB, mud)
  781. players[s1id]['lastCombatAction'] = int(time.time())
  782. return
  783. # Do damage to the NPC here
  784. if attackRoll(
  785. players[s1id]['luc'] +
  786. weaponProficiency(
  787. s1id,
  788. players,
  789. weaponType,
  790. characterClassDB)):
  791. damageValue, armorClass, damageDescription = calculateDamage(
  792. weaponDamage(
  793. s1id, players, itemsDB, weaponType, characterClassDB), weaponDefense(
  794. s2id, npcs, itemsDB, racesDB, weaponType, characterClassDB))
  795. npcWearsArmor(s2id, npcs, itemsDB)
  796. if roundsOfFire < 1:
  797. roundsOfFire = 1
  798. attackDescriptionFirst, attackDescriptionSecond = getAttackDescription(
  799. weaponType)
  800. if armorClass <= damageValue:
  801. if players[s1id]['hp'] > 0:
  802. modifierStr = ''
  803. for firingRound in range(roundsOfFire):
  804. modifier = randint(0, 10) + damageValue - armorClass
  805. damagePoints = players[s1id]['str'] + modifier
  806. if damagePoints < 0:
  807. damagePoints = 0
  808. npcs[s2id]['hp'] = npcs[s2id]['hp'] - damagePoints
  809. if len(modifierStr) == 0:
  810. modifierStr = modifierStr + str(damagePoints)
  811. else:
  812. modifierStr = modifierStr + \
  813. ' + ' + str(damagePoints)
  814. decreaseAffinityBetweenPlayers(npcs, s2id, players, s1id)
  815. decreaseAffinityBetweenPlayers(players, s1id, npcs, s2id)
  816. mud.send_message(
  817. s1id,
  818. 'You ' +
  819. attackDescriptionFirst +
  820. ' <f220>' +
  821. npcs[s2id]['name'] +
  822. '<r> for <b2><f15> * ' +
  823. modifierStr +
  824. ' * <r> points of ' +
  825. damageDescription +
  826. '\n')
  827. else:
  828. if players[s1id]['hp'] > 0:
  829. # Attack deflected by armor
  830. mud.send_message(
  831. s1id,
  832. 'You ' +
  833. attackDescriptionFirst +
  834. ' <f32><u>' +
  835. npcs[s2id]['name'] +
  836. '<r> but their armor deflects it.\n')
  837. else:
  838. players[s1id]['lastCombatAction'] = int(time.time())
  839. mud.send_message(
  840. s1id,
  841. 'You miss <f220>' +
  842. npcs[s2id]['name'] +
  843. '<r> completely!\n')
  844. players[s1id]['lastCombatAction'] = int(time.time())
  845. else:
  846. mud.send_message(
  847. s1id,
  848. '<f225>Suddenly you stop. It wouldn`t be a good idea to attack <u><f21>' +
  849. npcs[s2id]['name'] +
  850. '<r> at this time.\n')
  851. fightsCopy = deepcopy(fights)
  852. for (fight, pl) in fightsCopy.items():
  853. if fightsCopy[fight]['s1id'] == s1id and fightsCopy[fight]['s2id'] == s2id:
  854. del fights[fight]
  855. def runFightsBetweenNPCAndPlayer(
  856. mud,
  857. players: {},
  858. npcs: {},
  859. fights,
  860. fid,
  861. items: {},
  862. itemsDB: {},
  863. rooms: {},
  864. maxTerrainDifficulty,
  865. mapArea,
  866. clouds: {},
  867. racesDB: {},
  868. characterClassDB: {}):
  869. """Fight between NPC and player
  870. """
  871. s1id = fights[fid]['s1id']
  872. s2id = fights[fid]['s2id']
  873. # In the same room?
  874. if npcs[s1id]['room'] != players[s2id]['room']:
  875. return
  876. # is the player frozen?
  877. if npcs[s1id]['frozenStart'] > 0:
  878. mud.send_message(
  879. s2id,
  880. '<f220>' +
  881. npcs[s1id]['name'] +
  882. "<r> tries to attack but can't move\n")
  883. return
  884. currRoom = npcs[s1id]['room']
  885. weightDifficulty = int(playerInventoryWeight(s1id, npcs, itemsDB) / 20)
  886. temperatureDifficulty = getTemperatureDifficulty(
  887. currRoom, rooms, mapArea, clouds)
  888. terrainDifficulty = rooms[players[s2id]['room']
  889. ]['terrainDifficulty'] * 10 / maxTerrainDifficulty
  890. # Agility of NPC
  891. if int(time.time()) < npcs[s1id]['lastCombatAction'] + 10 - npcs[s1id]['agi'] - armorAgility(
  892. s1id, npcs, itemsDB) + terrainDifficulty + temperatureDifficulty + weightDifficulty:
  893. return
  894. npcs[s1id]['isInCombat'] = 1
  895. players[s2id]['isInCombat'] = 1
  896. npcUpdateLuck(s1id, npcs, items, itemsDB)
  897. if npcWieldsWeapon(mud, s2id, s1id, npcs, items, itemsDB):
  898. return
  899. weaponID, weaponType, roundsOfFire = getWeaponHeld(s1id, npcs, itemsDB)
  900. # Do the damage to PC here
  901. if attackRoll(npcs[s1id]['luc'] +
  902. weaponProficiency(s1id, npcs, weaponType, characterClassDB)):
  903. damageValue, armorClass, damageDescription = calculateDamage(
  904. weaponDamage(
  905. s1id, npcs, itemsDB, weaponType, characterClassDB), weaponDefense(
  906. s2id, players, itemsDB, racesDB, weaponType, characterClassDB))
  907. if roundsOfFire < 1:
  908. roundsOfFire = 1
  909. attackDescriptionFirst, attackDescriptionSecond = getAttackDescription(
  910. weaponType)
  911. if armorClass <= damageValue:
  912. if npcs[s1id]['hp'] > 0:
  913. modifierStr = ''
  914. for firingRound in range(roundsOfFire):
  915. modifier = randint(
  916. 0, 10) + damageValue - armorClass - npcs[s1id]['tempHitPoints']
  917. damagePoints = npcs[s1id]['str'] + modifier
  918. if damagePoints < 0:
  919. damagePoints = 0
  920. players[s2id]['hp'] = players[s2id]['hp'] - damagePoints
  921. if len(modifierStr) == 0:
  922. modifierStr = modifierStr + str(damagePoints)
  923. else:
  924. modifierStr = modifierStr + ' + ' + str(damagePoints)
  925. decreaseAffinityBetweenPlayers(npcs, s1id, players, s2id)
  926. decreaseAffinityBetweenPlayers(players, s2id, npcs, s1id)
  927. mud.send_message(
  928. s2id,
  929. '<f220>' +
  930. npcs[s1id]['name'] +
  931. '<r> has ' +
  932. attackDescriptionSecond +
  933. ' you for <f15><b88> * ' +
  934. modifierStr +
  935. ' * <r> points of ' +
  936. damageDescription +
  937. '.\n')
  938. else:
  939. mud.send_message(
  940. s2id,
  941. '<f220>' +
  942. npcs[s1id]['name'] +
  943. '<r> has ' +
  944. attackDescriptionSecond +
  945. ' you but it is deflected by your armor.\n')
  946. else:
  947. npcs[s1id]['lastCombatAction'] = int(time.time())
  948. mud.send_message(
  949. s2id,
  950. '<f220>' +
  951. npcs[s1id]['name'] +
  952. '<r> has missed you completely!\n')
  953. npcs[s1id]['lastCombatAction'] = int(time.time())
  954. def runFights(
  955. mud,
  956. players: {},
  957. npcs: {},
  958. fights,
  959. items: {},
  960. itemsDB: {},
  961. rooms: {},
  962. maxTerrainDifficulty,
  963. mapArea: [],
  964. clouds: {},
  965. racesDB: {},
  966. characterClassDB: {}):
  967. """Handles fights
  968. """
  969. for (fid, pl) in list(fights.items()):
  970. # PC -> PC
  971. if fights[fid]['s1type'] == 'pc' and fights[fid]['s2type'] == 'pc':
  972. runFightsBetweenPlayers(
  973. mud,
  974. players,
  975. npcs,
  976. fights,
  977. fid,
  978. itemsDB,
  979. rooms,
  980. maxTerrainDifficulty,
  981. mapArea,
  982. clouds,
  983. racesDB,
  984. characterClassDB)
  985. # PC -> NPC
  986. elif fights[fid]['s1type'] == 'pc' and fights[fid]['s2type'] == 'npc':
  987. runFightsBetweenPlayerAndNPC(
  988. mud,
  989. players,
  990. npcs,
  991. fights,
  992. fid,
  993. itemsDB,
  994. rooms,
  995. maxTerrainDifficulty,
  996. mapArea,
  997. clouds,
  998. racesDB,
  999. characterClassDB)
  1000. # NPC -> PC
  1001. elif fights[fid]['s1type'] == 'npc' and fights[fid]['s2type'] == 'pc':
  1002. runFightsBetweenNPCAndPlayer(
  1003. mud,
  1004. players,
  1005. npcs,
  1006. fights,
  1007. fid,
  1008. items,
  1009. itemsDB,
  1010. rooms,
  1011. maxTerrainDifficulty,
  1012. mapArea,
  1013. clouds,
  1014. racesDB,
  1015. characterClassDB)
  1016. # NPC -> NPC
  1017. elif fights[fid]['s1type'] == 'npc' and fights[fid]['s2type'] == 'npc':
  1018. test = 1