combat.py 41 KB

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