PageRenderTime 50ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/game/engine/subsystems/tunnel.lua

https://bitbucket.org/elcugo/t-engine
Lua | 952 lines | 789 code | 104 blank | 59 comment | 76 complexity | a21e79b02dd2619c9aa5699674f07952 MD5 | raw file
Possible License(s): GPL-2.0
  1. -----------------------------------------------------------------------
  2. -- Tunnelling subsystem
  3. -----------------------------------------------------------------------
  4. constant("tunnel", {})
  5. settag(tunnel, TAG_NAMESPACE)
  6. safe_new_flag("TUNNELABLE")
  7. safe_new_flag("NO_TUNNEL_MSG")
  8. safe_new_flag("DIGGER_MIMIC")
  9. safe_new_flag("TUNNEL_FAIL_MSG")
  10. safe_new_flag("TUNNEL_WORKING_MSG")
  11. safe_new_flag("TUNNEL_DONE_MSG")
  12. safe_new_flag("ON_TUNNEL_MISMATCH", true)
  13. safe_new_flag("DIG_POWER")
  14. safe_new_flag("ADD_DIG_POWER")
  15. hook.new_hook_type("GET_DIGGER")
  16. tunnel.extra_msgs = {}
  17. --------------------------------------------------------------------------
  18. --------------------------------------------------------------------------
  19. --
  20. -- Customizable subsystem stuff
  21. --
  22. function tunnel.max_by_flags(array, feat)
  23. local found = false
  24. local val = -infinity
  25. for i = 1, flag_max_key(feat.flags) do
  26. if feat.flags[i] and array[i] then
  27. val = max(val, array[i])
  28. found = true
  29. end
  30. end
  31. if found then
  32. return val
  33. end
  34. end -- tunnel.max_by_flags()
  35. function tunnel.index_by_flags(array, feat)
  36. for i = 1, flag_max_key(feat.flags) do
  37. if feat.flags[i] and array[i] then
  38. return array[i]
  39. end
  40. end
  41. end -- tunnel.index_by_flags()
  42. tunnel.no_digger_msgs_by_flag = {}
  43. tunnel.no_tunnel_msgs = {}
  44. tunnel.working_msgs_by_feat = {}
  45. tunnel.working_msgs_by_flag = {}
  46. tunnel.done_msgs_by_feat = {}
  47. tunnel.done_msgs_by_flag = {}
  48. tunnel.no_progress_msgs_by_flags = {}
  49. tunnel.min_power_by_flags = {}
  50. tunnel.hardness_by_flags = {}
  51. function tunnel.add_no_digger_msgs_by_flag(t)
  52. for k, v in t do
  53. tunnel.no_digger_msgs_by_flag[k] = v
  54. end
  55. end
  56. function tunnel.add_no_tunnel_msgs(t)
  57. for k, v in t do
  58. tunnel.no_tunnel_msgs[k] = v
  59. end
  60. end
  61. function tunnel.add_working_msgs_by_feat(t)
  62. for k, v in t do
  63. tunnel.working_msgs_by_feat[k] = v
  64. end
  65. end
  66. function tunnel.add_working_msgs_by_flag(t)
  67. for k, v in t do
  68. tunnel.working_msgs_by_flag[k] = v
  69. end
  70. end
  71. function tunnel.add_done_msgs_by_feat(t)
  72. for k, v in t do
  73. tunnel.done_msgs_by_feat[k] = v
  74. end
  75. end
  76. function tunnel.add_done_msgs_by_flag(t)
  77. for k, v in t do
  78. tunnel.done_msgs_by_flag[k] = v
  79. end
  80. end
  81. function tunnel.add_no_progress_msgs_by_flags(t)
  82. for k, v in t do
  83. tunnel.no_progress_msgs_by_flags[k] = v
  84. end
  85. end
  86. function tunnel.add_min_power_by_flags(t)
  87. for k, v in t do
  88. tunnel.min_power_by_flags[k] = v
  89. end
  90. end
  91. function tunnel.add_hardness_by_flags(t)
  92. for k, v in t do
  93. tunnel.hardness_by_flags[k] = v
  94. end
  95. end
  96. -- Have to wait features are loaded to use them.
  97. hook(hook.INFO_DATA_LOADED,
  98. function()
  99. tunnel.add_no_tunnel_msgs {
  100. [FEAT_LESS] = "You cannot tunnel a staircase.",
  101. [FEAT_MORE] = "You cannot tunnel a staircase.",
  102. [FEAT_WAY_MORE] = "You cannot tunnel a pathway.",
  103. [FEAT_WAY_MORE] = "You cannot tunnel a pathway.",
  104. [FEAT_SHAFT_DOWN] = "You cannot tunnel a shaft.",
  105. [FEAT_SHAFT_UP] = "You cannot tunnel a shaft."
  106. }
  107. tunnel.add_working_msgs_by_feat {
  108. [FEAT_RUBBLE] = "You dig in the @feat@."
  109. }
  110. tunnel.add_done_msgs_by_feat {
  111. [FEAT_RUBBLE] = "You have removed the @feat@."
  112. }
  113. end)
  114. tunnel.add_working_msgs_by_flag {
  115. [FLAG_DOOR] = "You bash the door.",
  116. [FLAG_SUBST_RUBBLE] = "You dig in the @feat@."
  117. [FLAG_SUBST_GRANITE] = "You tunnel into the @feat@."
  118. }
  119. tunnel.add_done_msgs_by_flag {
  120. [FLAG_DOOR] = "You have bashed in the door.",
  121. [FLAG_SUBST_RUBBLE] = "You have removed the @feat@.",
  122. [FLAG_SUBST_GRANITE] = "You have removed the @feat@."
  123. }
  124. tunnel.add_min_power_by_flags {
  125. [FLAG_SUBST_RUBBLE] = 0,
  126. [FLAG_SUBST_DOOR] = 30,
  127. [FLAG_JAMMED] = 40,
  128. [FLAG_SUBST_GRANITE] = 40,
  129. }
  130. tunnel.add_hardness_by_flags {
  131. [FLAG_SUBST_RUBBLE] = 200,
  132. [FLAG_SUBST_DOOR] = 1200,
  133. [FLAG_JAMMED] = 1600,
  134. [FLAG_SUBST_GRANITE] = 1600,
  135. }
  136. ----------------------------------------
  137. tunnel.no_digger_msg = get_subsystem_param("tunnel", "no_digger_msg")
  138. function tunnel.format_tunnel_msg(msg, feat, how_name, how_plur)
  139. how_name = how_name or tunnel.how_name or "NO DIGGER"
  140. how_plur = how_plur or tunnel.how_plur
  141. msg = gsub(msg, "(@Digger@)", strcap(how_name))
  142. msg = gsub(msg, "(@digger@)", how_name)
  143. msg = gsub(msg, "(@Feat@)", strcap(feat.name))
  144. msg = gsub(msg, "(@feat@)", feat.name)
  145. if how_plur then
  146. msg = gsub(msg, "(@s@)", "")
  147. msg = gsub(msg, "(@es@)", "")
  148. msg = gsub(msg, "(@it@)", "they")
  149. msg = gsub(msg, "(@does@)", "do")
  150. msg = gsub(msg, "(@doesn't@)", "don't")
  151. msg = gsub(msg, "(@has@)", "have")
  152. msg = gsub(msg, "(@hasn't@)", "haven't")
  153. else
  154. msg = gsub(msg, "(@s@)", "s")
  155. msg = gsub(msg, "(@es@)", "es")
  156. msg = gsub(msg, "(@it@)", "it")
  157. msg = gsub(msg, "(@does@)", "does")
  158. msg = gsub(msg, "(@doesn't@)", "doesn't")
  159. msg = gsub(msg, "(@has@)", "has")
  160. msg = gsub(msg, "(@hasn't@)", "hasn't")
  161. end
  162. return msg
  163. end -- tunnel.format_tunnel_msg
  164. ----------------------------------------------------
  165. -- First, we have to add a FLAG_ADD_DIG_POWER flagset back into the
  166. -- intrinsic flags at the start of each bonus calculation round,
  167. -- so it'll be there to add to.
  168. hook(hook.CALC_BONUS_BEGIN,
  169. function()
  170. player.intrinsic_flags[FLAG_ADD_DIG_POWER] = flag_new(2)
  171. end)
  172. function tunnel.default_get_dig_power(how_type, how_what, known, feat,
  173. y, x)
  174. local power = 0
  175. if how_type == kill_wall.how_type.INTRINSIC then
  176. local flags = player.intrinsic_flags[FLAG_KILL_WALL]
  177. if flags and flag_intersects(flags, feat.flags) then
  178. -- Engine default is that monsters always kill walls
  179. -- with 100% chance.
  180. power = infinity
  181. else
  182. return nil
  183. end
  184. elseif how_type == kill_wall.how_type.OBJECT then
  185. local flags = how_what.flags[FLAG_DIG_POWER]
  186. if flags then
  187. local tmp = {val = 0}
  188. foreach_flags(flags,
  189. function(flags, key)
  190. if %feat.flags[key] and
  191. (not %known or flag_is_known(flags, key))
  192. then
  193. %tmp.val = max(%tmp.val, flags[key])
  194. end
  195. end) -- foreach_flags
  196. power = tmp.val
  197. else
  198. return nil
  199. end
  200. else
  201. error("Only know how to handle object and intrinsic " ..
  202. "kill_wall types.")
  203. end
  204. -- Don't add in bonus digging power if it needs to be known,
  205. -- since determining which bonuses are known and which aren't
  206. -- would be a big pain.
  207. if known then
  208. return power
  209. end
  210. -- No need to chance it it's infinite
  211. if power >= infinity then
  212. return power
  213. end
  214. local flags = player.intrinsic_flags[FLAG_ADD_DIG_POWER]
  215. if not flags then
  216. return power
  217. end
  218. local tmp = {min = 0, max=0}
  219. foreach_flags(flags,
  220. function(flags, key)
  221. if %feat.flags[key] then
  222. %tmp.min = min(%tmp.min, flags[key])
  223. %tmp.max = max(%tmp.max, flags[key])
  224. end
  225. end) -- foreach_flags
  226. power = power + tmp.max + tmp.min
  227. return power
  228. end -- tunnel.default_get_dig_power()
  229. tunnel.get_dig_power = get_subsystem_param("tunnel",
  230. "get_dig_power") or
  231. tunnel.default_get_dig_power
  232. ------------------------------------------
  233. tunnel.is_plural = get_subsystem_param("tunnel", "is_plural") or
  234. function(obj)
  235. return false
  236. end
  237. tunnel.get_digger_name = get_subsystem_param("tunnel",
  238. "get_digger_name") or
  239. function(how_type, how_what)
  240. if how_type == kill_wall.how_type.INTRINSIC then
  241. return "your hands", true
  242. end
  243. if how_type == kill_wall.how_type.OBJECT then
  244. local obj = how_what
  245. if is_artifact(obj) then
  246. return "the " .. object_desc(obj), tunnel.is_plural(obj)
  247. else
  248. return "your " .. object_desc(obj), tunnel.is_plural(obj)
  249. end
  250. end
  251. return "CAN'T GET DIGGER NAME", false
  252. end -- tunnel.get_digger_name()
  253. tunnel.get_digger_msgs = get_subsystem_param("tunnel",
  254. "get_digger_msgs") or
  255. function(feat, y, x, how_type, how_what)
  256. return nil, nil
  257. end -- tunnel.get_digger_msgs()
  258. tunnel.get_digger = get_subsystem_param("tunnel", "get_digger") or
  259. function(feat, y, x)
  260. local whats = {}
  261. if has_flag(feat, FLAG_DIGGER_MIMIC) then
  262. feat = f_info(feat.flags[FLAG_DIGGER_MIMIC])
  263. end
  264. -- Can player do it without any object?
  265. local power = tunnel.get_dig_power(kill_wall.how_type.INTRINSIC, nil,
  266. true, feat, y, x)
  267. if power and power >= 1 then
  268. local name, plur =
  269. tunnel.get_digger_name(kill_wall.how_type.INTRINSIC)
  270. tinsert(whats, {
  271. power = power,
  272. how_type = kill_wall.how_type.INTRINSIC,
  273. how_what = nil,
  274. how_name = name,
  275. how_plur = plur,
  276. })
  277. end
  278. -- Go through equipment looking for diggers.
  279. for_inventory(player, INVEN_PACK, INVEN_TOTAL,
  280. function(obj, pos, slot, item)
  281. local power = tunnel.get_dig_power(kill_wall.how_type.OBJECT,
  282. obj, true, %feat, %y, %x)
  283. if power and power >= 1 then
  284. local name, plur =
  285. tunnel.get_digger_name(kill_wall.how_type.OBJECT,
  286. obj)
  287. tinsert(%whats, {
  288. power = power,
  289. how_type = kill_wall.how_type.OBJECT,
  290. how_what = obj,
  291. how_name = name,
  292. how_plur = plur,
  293. })
  294. end -- if power and power >= 1 then
  295. end) -- for_inventory()
  296. -- Let hooks add potential diggers.
  297. hook.process(hook.GET_DIGGER, feat, y, x, whats)
  298. if getn(whats) == 0 then
  299. -- No diggers found.
  300. return nil
  301. end
  302. -- Pick digger with biggest power.
  303. sort(whats,
  304. function(a, b)
  305. return a.power > b.power -- Sort in reverse order.
  306. end)
  307. local digger = whats[1]
  308. local tunnel_working_msg, tunnel_done_msg
  309. if digger.how_type == kill_wall.how_type.OBJECT then
  310. tunnel_working_msg = digger.how_what.flags[FLAG_TUNNEL_WORKING_MSG]
  311. tunnel_done_msg = digger.how_what.flags[FLAG_TUNNEL_DONE_MSG]
  312. end
  313. tunnel_working_msg, tunnel_done_msg =
  314. tunnel.get_digger_msgs(feat, y, x, digger.how_type,
  315. digger.how_what)
  316. digger.tunnel_working_msg = digger.tunnel_working_msg or
  317. tunnel_working_msg
  318. digger.tunnel_done_msg = digger.tunnel_done_msg or
  319. tunnel_done_msg
  320. return digger
  321. end -- tunnel.get_digger()
  322. ------------------------------
  323. tunnel.progress_msg_chance = get_subsystem_param("tunnel",
  324. "progress_msg_chance") or
  325. function(power, hardness, feat)
  326. if command_rep == 0 or power < 1 then
  327. return 100
  328. else
  329. return 0
  330. end
  331. end
  332. tunnel.no_progress_msg = get_subsystem_param("tunnel",
  333. "no_progress_msg") or
  334. "@Digger@ @does@ not have any effect on the @feat@."
  335. tunnel.do_progress_msg = get_subsystem_param("tunnel",
  336. "do_progress_msg") or
  337. function(power, hardness, feat)
  338. local msg
  339. if power < 1 then
  340. msg = tunnel.index_by_flags(tunnel.no_progress_msgs_by_flags,
  341. feat)
  342. msg = msg or tunnel.no_progress_msg
  343. else
  344. local avg_reps = hardness / power
  345. if avg_reps >= 1000 then
  346. msg = "This will take an extremely long time."
  347. elseif avg_reps >= 750 then
  348. msg = "This will take a very, very long time."
  349. elseif avg_reps >= 500 then
  350. msg = "This will take a very long time."
  351. elseif avg_reps >= 250 then
  352. msg = "This will take a long time."
  353. elseif avg_reps >= 100 then
  354. msg = "This will take some time."
  355. elseif avg_reps >= 50 then
  356. msg = "This should be over soon."
  357. elseif avg_reps >= 10 then
  358. msg = "This should be over very soon."
  359. else
  360. msg = "This should be over in no time."
  361. end
  362. end
  363. if msg then
  364. if command_rep == 0 or power < 1 then
  365. tinsert(tunnel.extra_msgs, tunnel.format_tunnel_msg(msg, feat))
  366. else
  367. message(tunnel.format_tunnel_msg(msg, feat))
  368. end
  369. end
  370. end -- tunnel.do_progress_msg()
  371. tunnel.kill_wall_result = get_subsystem_param("tunnel",
  372. "kill_wall_result") or
  373. function(y, x)
  374. local c_ptr = cave(y, x)
  375. local feat = f_info(c_ptr.feat)
  376. local min_power = feat.flags[FLAG_TUNNELABLE]
  377. local hardness = flag_get2(feat.flags, FLAG_TUNNELABLE)
  378. if not min_power then
  379. min_power = tunnel.max_by_flags(tunnel.min_power_by_flags,
  380. feat)
  381. hardness = tunnel.max_by_flags(tunnel.hardness_by_flags,
  382. feat)
  383. end
  384. assert(min_power, "No min_power defined for " .. feat.name)
  385. assert(hardness, "No hardness defined for " .. feat.name)
  386. local how_type = tunnel.how_type
  387. local how_what = tunnel.how_what
  388. local power = tunnel.get_dig_power(how_type, how_what, false,
  389. feat, y, x)
  390. power = power or 0
  391. power = power - min_power
  392. if power > rng.number(hardness) then
  393. return KILLWALL_DONE, false
  394. end
  395. if c_ptr.mimic ~= 0 then
  396. feat = f_info(c_ptr.mimic)
  397. end
  398. if rng.percent(tunnel.progress_msg_chance(power, hardness)) then
  399. tunnel.do_progress_msg(power, hardness, feat)
  400. end
  401. if power < 1 then
  402. return KILLWALL_FAILURE, false
  403. end
  404. return KILLWALL_WORKING, false
  405. end -- tunnel.kill_wall_result()
  406. tunnel.do_sound = get_subsystem_param("tunnel", "do_sound") or
  407. function(result, y, x, dir, feat_real, feat_fake)
  408. end
  409. -----------------------------------------------------------------------------
  410. -----------------------------------------------------------------------------
  411. --
  412. -- Actually doing the tunneling
  413. --
  414. function tunnel.exec(y, x, dir, feat_real, feat_fake)
  415. local f_ptr_real = f_info(feat_real)
  416. local f_ptr_fake = iif(feat_fake, f_info(feat_fake), nil)
  417. local f_ptr = f_ptr_fake or f_ptr_real
  418. local feat = feat_fake or feat_real
  419. if has_flag(f_ptr, FLAG_NO_TUNNEL_MSG) or tunnel.no_tunnel_msgs[feat] or
  420. (has_flag(f_ptr, FLAG_PERMANENT) and has_flag(f_ptr, FLAG_WALL))
  421. then
  422. local msg = f_ptr.flags[FLAG_NO_TUNNEL_MSG]
  423. msg = msg or tunnel.no_tunnel_msgs[feat]
  424. msg = msg or "The @feat@ is too hard to tunnel through."
  425. message(tunnel.format_tunnel_msg(msg, f_ptr))
  426. return KILLWALL_FAILURE
  427. end
  428. energy_use = get_player_energy(SPEED_DIG)
  429. -- Digger saved from previous repitition? Saving digger might
  430. -- not be needed for speed purposes, but the digger shouldn't
  431. -- change during a single repitition of digging because of
  432. -- other stuff happening in the dungeon.
  433. if not tunnel.digger_info then
  434. -- No digger saved, get one
  435. local digger_info = tunnel.get_digger(f_ptr, y, x)
  436. if not digger_info then
  437. if has_flag(f_ptr, FLAG_WALL) or not
  438. (has_flag(f_ptr, FLAG_KILL_WALL_PRE) or
  439. has_flag(f_ptr, FLAG_KILL_WALL_DO))
  440. then
  441. -- No digger found, do nothing
  442. local msg =
  443. tunnel.index_by_flags(tunnel.no_digger_msgs_by_flag,
  444. f_ptr)
  445. msg = msg or tunnel.no_digger_msg
  446. if msg then
  447. msg = tunnel.format_tunnel_msg(msg, f_ptr)
  448. message(msg)
  449. end
  450. energy_use = 0
  451. return KILLWALL_FAILURE
  452. else
  453. -- We've been called even though the square isn't a
  454. -- wall. The square must be some weird type of
  455. -- feature which defines a KILL_WALL_PRE or
  456. -- KILL_WALL_DO even though it isn't a wall.
  457. digger_info = {
  458. how_type = kill_wall.how_type.NOTHING
  459. how_name = ""
  460. }
  461. end
  462. end
  463. local how_type = digger_info.how_type
  464. local how_what = digger_info.how_what
  465. local power = tunnel.get_dig_power(how_type, how_what, false,
  466. f_ptr_real, y, x)
  467. if (not power or power < 1) and how_type == kill_wall.how_type.NOTHING
  468. then
  469. power = 1
  470. end
  471. if f_ptr_fake and (digger_info.mismatch_func or
  472. has_flag(f_ptr_real, FLAG_ON_TUNNEL_MISMATCH) or
  473. (how_type == kill_wall.how_type.OBJECT and
  474. has_flag(how_what, FLAG_ON_TUNNEL_MISMATCH)))
  475. then
  476. local digger_info_real = tunnel.get_digger(f_ptr_real, y, x)
  477. if digger_info_real.how_type ~= how_type or
  478. digger_info_real.how_what ~= how_what
  479. then
  480. local ret
  481. local func = digger_info.mismatch_func
  482. if func then
  483. ret = func(y, x, dir, f_ptr_real, f_ptr_fake,
  484. digger_info, digger_info_real)
  485. if ret then
  486. return ret
  487. end
  488. end
  489. local flag = FLAG_ON_TUNNEL_MISMATCH
  490. if has_flag(r_ptr_real, flag) then
  491. func =
  492. get_function_registry_from_flag(f_ptr_real.flags,
  493. flag)
  494. ret = func(y, x, dir, f_ptr_real, f_ptr_fake,
  495. digger_info, digger_info_real)
  496. if ret then
  497. return ret
  498. end
  499. end
  500. if how_type == kill_wall.how_type.OBJECT then
  501. ret = item_hooks.process_one(how_what, flag, y, x, dir,
  502. f_ptr_real, f_ptr_fake,
  503. digger_info,
  504. digger_info_real)
  505. if ret then
  506. return ret
  507. end
  508. end
  509. end -- if diggers don't match then
  510. end -- if f_ptr_fake and ... then
  511. -- Save stuff for future repititions (and as pseudo-global
  512. -- variables for other code to look at)
  513. tunnel.how_type = digger_info.how_type
  514. tunnel.how_what = digger_info.how_what
  515. tunnel.how_name = digger_info.how_name
  516. tunnel.how_plur = digger_info.how_plur
  517. tunnel.power = power
  518. tunnel.digger_info = digger_info
  519. end -- if not tunnel.how_type then
  520. -- Now we have a digger, so do it.
  521. local ret
  522. ret = kill_wall.do_kill(y, x, WHO_PLAYER, tunnel.how_type,
  523. tunnel.how_what)
  524. if ret == KILLWALL_DONE then
  525. -- handle_stuff() to get CAVE_MARK updated.
  526. handle_stuff()
  527. local c_ptr = cave(y, x)
  528. if c_ptr.info & CAVE_MARK ~= 0 then
  529. -- Re-map the spot if we can see it.
  530. note_spot(y, x)
  531. lite_spot(y, x)
  532. end
  533. -- If we can see the result, then we give messages according
  534. -- to what it really was. If we can't see it and it was
  535. -- mimimicing something, we give messages according to what
  536. -- the player thought it was.
  537. if c_ptr.info & CAVE_MARK ~= 0 then
  538. f_ptr = f_ptr_real
  539. feat = feat_real
  540. end
  541. local msg = tunnel.digger_info.tunnel_done_msg
  542. msg = msg or f_ptr.flags[FLAG_TUNNEL_DONE_MSG]
  543. msg = msg or tunnel.done_msgs_by_feat[feat]
  544. msg = msg or
  545. tunnel.index_by_flags(tunnel.done_msgs_by_flag,
  546. f_ptr)
  547. msg = msg or "You have removed the @feat@."
  548. -- HACK: handle destroying secret doors.
  549. if c_ptr.info & CAVE_MARK ~= 0 and
  550. has_flag(f_ptr_real, FLAG_SECRET) and
  551. c_ptr.feat == FEAT_BROKEN
  552. then
  553. msg = "The @feat@ appears to have actually been a secret door!"
  554. end
  555. msg = tunnel.format_tunnel_msg(msg, f_ptr,
  556. tunnel.how_name,
  557. tunnel.how_plur)
  558. message(msg)
  559. elseif ret == KILLWALL_WORKING then
  560. local msg = tunnel.digger_info.tunnel_working_msg
  561. msg = msg or f_ptr.flags[FLAG_TUNNEL_WORKING_MSG]
  562. msg = msg or tunnel.working_msgs_by_feat[feat]
  563. msg = msg or
  564. tunnel.index_by_flags(tunnel.working_msgs_by_flag,
  565. f_ptr)
  566. if msg then
  567. msg = tunnel.format_tunnel_msg(msg, f_ptr,
  568. tunnel.how_name,
  569. tunnel.how_plur)
  570. message(msg)
  571. end
  572. end
  573. tunnel.do_sound(ret, y, x, dir, feat_real, feat_fake)
  574. tunnel.handle_extra_msgs()
  575. return ret
  576. end -- tunnel.exec()
  577. function tunnel.handle_extra_msgs()
  578. if not tunnel.extra_msgs then
  579. tunnel.extra_msgs = {}
  580. end
  581. for i = 1, getn(tunnel.extra_msgs) do
  582. message(tunnel.extra_msgs[i])
  583. end
  584. tunnel.extra_msgs = {}
  585. end -- tunnel.handle_extra_msgs()
  586. -- Override default kill_wall.player_result()
  587. function kill_wall.player_result(y, x, how_type, how_what)
  588. local result, did_replace =
  589. tunnel.kill_wall_result_aux(y, x, how_type, how_what)
  590. return result, did_replace
  591. end
  592. -- Invoke hooks, then invoke subsystem parameter tunnel.kill_wall_result()
  593. function tunnel.kill_wall_result_aux(y, x, how_type, how_what)
  594. if how_type ~= kill_wall.how_type.OBJECT then
  595. local result, did_replace =
  596. tunnel.kill_wall_result(y, x)
  597. result = result or KILLWALL_FAILURE
  598. return result, did_replace
  599. end
  600. -- Is the player digging away at a wall that isn't reall there?
  601. local c_ptr = cave(y, x)
  602. if c_ptr.mimic ~= 0 and not has_flag(f_info(c_ptr.feat), FLAG_WALL) then
  603. local msg = "@Digger@ passes through the @feat@ like it isn't there."
  604. message(tunnel.format_tunnel_msg(msg, f_info(c_ptr.mimic)))
  605. return KILLWALL_FAILURE
  606. end
  607. local obj = how_what
  608. local did_replace = {val = false}
  609. local result, ret
  610. result = item_hooks.process_one(obj, FLAG_KILL_WALL_PRE,
  611. y, x, did_replace)
  612. if result then
  613. -- Whatever happened happend in the pre hook, do nothing
  614. elseif has_flag(obj, FLAG_KILL_WALL_DO) then
  615. -- Object will do the digging itself
  616. result = item_hooks.process_one(obj, FLAG_KILL_WALL_DO,
  617. y, x, did_replace)
  618. else
  619. -- Do it normally with paramater-izable function
  620. result, did_replace.val =
  621. tunnel.kill_wall_result(y, x)
  622. end
  623. result = result or KILLWALL_FAILURE
  624. -- Inform object of result, let it change return value.
  625. if result == KILLWALL_FAILURE then
  626. ret = item_hooks.process_one(obj, FLAG_KILL_WALL_FAIL,
  627. y, x, did_replace)
  628. elseif result == KILLWALL_WORKING then
  629. ret = item_hooks.process_one(obj, FLAG_KILL_WALL_WORKING,
  630. y, x, did_replace)
  631. elseif result == KILLWALL_DONE then
  632. ret = item_hooks.process_one(obj, FLAG_KILL_WALL_DONE,
  633. y, x, did_replace)
  634. end
  635. ret = ret or result
  636. return ret, did_replace.val
  637. end -- tunnel.kill_wall_result_aux()
  638. function tunnel.do_tunnel(y, x, dir)
  639. tunnel.was_disturbed = true
  640. local c_ptr = cave(y, x)
  641. if c_ptr.info & CAVE_MARK == 0 then
  642. -- Player doesn't know what the grid is, so doesn't know what
  643. -- to use to tunnel through it, or what sort of alteration to
  644. -- make to it at all.
  645. if tunnel.no_mark_msg then
  646. message(tunnel.no_mark_msg)
  647. end
  648. return false
  649. end
  650. local f_ptr_real, f_ptr_fake, feat_real, feat_fake
  651. feat_real = c_ptr.feat
  652. f_ptr_real = f_info(feat_real)
  653. if has_flag(f_ptr_real, FLAG_FIND_INTO) then
  654. feat_real = f_ptr_real.flags[FLAG_FIND_INTO]
  655. f_ptr_real = f_info(feat_real)
  656. end
  657. if c_ptr.mimic ~= 0 and c_ptr.mimic ~= c_ptr.feat then
  658. feat_fake = c_ptr.mimic
  659. f_ptr_fake = f_info(feat_fake)
  660. end
  661. local f_ptr = f_ptr_fake or f_ptr_real
  662. local feat = feat_fake or feat_real
  663. if tunnel.prev_feat and tunnel.prev_feat ~= feat then
  664. -- What we were tunnelling suddenly changed on us, stop.
  665. tunnel.prev_feat = nil
  666. tunnel.how_type = nil
  667. term.disturb(0, 0)
  668. tunnel.flsuh(true)
  669. return true, false
  670. end
  671. -- Let other code deal with opening or bashing doors.
  672. if has_flag(f_ptr, FLAG_DOOR) and not has_flag(f_ptr, FLAG_SECRET) then
  673. tunnel.flsuh()
  674. return false
  675. end
  676. -- We can tunnel a non-wall if it has a hook defined for
  677. -- KILL_WALL_PRE or KILL_WALL_DO.
  678. if not (has_flag(f_ptr, FLAG_WALL) or
  679. has_flag(f_ptr, FLAG_KILL_WALL_PRE) or
  680. has_flag(f_ptr, FLAG_KILL_WALL_DO))
  681. then
  682. -- "Alter grid" includes disarming traps. But if there
  683. -- are no traps on the grid, then the non-wall grid might
  684. -- want to give a message about how it can't be tunneled.
  685. if get_num_location_traps(y, x, true, FACTION_PLAYER,
  686. false, true) == 0
  687. then
  688. local msg = f_ptr.flags[FLAG_NO_TUNNEL_MSG]
  689. msg = msg or tunnel.no_tunnel_msgs[feat]
  690. if msg then
  691. if has_flag(f_ptr, FLAG_WALL) or
  692. has_flag(f_ptr, FLAG_DIGGER_MIMIC)
  693. then
  694. tunnel.exec(y, x, dir, feat_real, feat_fake)
  695. else
  696. message(tunnel.format_tunnel_msg(msg, f_ptr))
  697. end
  698. tunnel.flsuh()
  699. return true, false
  700. end
  701. end
  702. tunnel.flsuh()
  703. return false
  704. end
  705. tunnel.prev_feat = feat
  706. if tunnel.exec(y, x, dir, feat_real, feat_fake) == KILLWALL_WORKING then
  707. -- Not done, return TRUE to caller to let it know that it should
  708. -- keep going with repeats if auto-repetition is on.
  709. tunnel.flsuh()
  710. return true, true
  711. else
  712. -- Either failed or done, so no need to repeat command anymore.
  713. tunnel.flsuh(true)
  714. return true, false
  715. end
  716. end
  717. function tunnel.flsuh(force)
  718. if force or tunnel.was_disturbed then
  719. tunnel.how_type = nil
  720. tunnel.how_what = nil
  721. tunnel.how_name = nil
  722. tunnel.prev_feat = nil
  723. tunnel.power = nil
  724. tunnel.digger_info = nil
  725. tunnel.handle_extra_msgs()
  726. end
  727. tunnel.was_disturbed = false
  728. end -- tunnel.flsuh()
  729. ----------------
  730. -- Various hooks
  731. ----------------
  732. hook(hook.ALTER_GRID, tunnel.do_tunnel)
  733. if not get_subsystem_param("tunnel", "no_key_bind") then
  734. hook(hook.KEYPRESS, function(key)
  735. if key == strbyte('T') then
  736. local y, x, dir, ret
  737. local more = false
  738. -- Allow repeated command
  739. if command_arg > 0 then
  740. -- Set repeat count
  741. command_rep = command_arg - 1
  742. -- Redraw the state
  743. player.redraw[FLAG_PR_STATE] = true
  744. -- Cancel the arg
  745. command_arg = 0
  746. end
  747. -- Get a direction
  748. ret, dir = get_rep_dir()
  749. if not ret then
  750. term.disturb(0, 0)
  751. tunnel.flsuh()
  752. return true
  753. end
  754. -- Get location
  755. y = player.py + ddy(dir)
  756. x = player.px + ddx(dir)
  757. more = tunnel.do_tunnel(y, x, dir)
  758. -- Cancel repetition unless we can continue
  759. if not more then term.disturb(0, 0) end
  760. tunnel.flsuh()
  761. return true
  762. end
  763. end)
  764. end
  765. -- Clear state if we stop tunneling due to interruption.
  766. hook(hook.DISTURB,
  767. function()
  768. if tunnel.digger_info then
  769. tunnel.was_disturbed = true
  770. end
  771. end)
  772. -- Handle automatic ("easy") tunneling if we run into a wall.
  773. hook(hook.HIT_WALL,
  774. function(y, x, dir)
  775. local c_ptr = cave(y, x)
  776. if not game_options.easy_tunnel or c_ptr.info & CAVE_MARK == 0 then
  777. return false
  778. end
  779. local feat_real = c_ptr.feat
  780. local feat_fake
  781. if c_ptr.mimic ~= 0 and c_ptr.mimc ~= feat_real then
  782. feat_fake = c_ptr.mimic
  783. end
  784. -- Wall must be must be diggable.
  785. local f_ptr = f_info(feat_fake or feat_real)
  786. if has_flag(f_ptr, FLAG_PERMANENT) or
  787. not (has_flag(f_ptr, FLAG_WALL) or
  788. has_flag(f_ptr, FLAG_KILL_WALL_PRE) or
  789. has_flag(f_ptr, FLAG_KILL_WALL_DO)) then
  790. return false
  791. end
  792. -- Is diggable, do it.
  793. tunnel.exec(y, x, dir, feat_real, feat_fake)
  794. term.disturb(0, 0)
  795. tunnel.flsuh()
  796. return true
  797. end)