Commit 31a28ca9516da114e0a1d760830bd7720311ead3

Authored by yutio888
2 parents 69cf964d c8a7ab2d

Merge remote-tracking branch 'upstream/master' into i18n

Showing 100 changed files with 923 additions and 64 deletions

Too many changes to show.

To preserve performance only 100 of 100+ files are displayed.

1.89 MB | W: | H:

2.05 MB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.37 MB | W: | H:

1.3 MB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.91 MB | W: | H:

1.89 MB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.21 KB | W: | H:

3.71 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.1 KB | W: | H:

3.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.19 KB | W: | H:

3.71 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.1 KB | W: | H:

3.61 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.08 KB | W: | H:

3.57 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.1 KB | W: | H:

3.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.2 KB | W: | H:

3.72 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.11 KB | W: | H:

3.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.21 KB | W: | H:

3.73 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.23 KB | W: | H:

3.72 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.11 KB | W: | H:

3.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.21 KB | W: | H:

3.72 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.11 KB | W: | H:

3.62 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.08 KB | W: | H:

3.57 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.11 KB | W: | H:

3.62 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.22 KB | W: | H:

3.73 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.11 KB | W: | H:

3.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.23 KB | W: | H:

3.74 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
... ... @@ -133,8 +133,8 @@ function _M:trigger(x, y, who)
133 133 else
134 134 local tname = who:getName()
135 135 local str =self.message
136   - str = str:gsub("@target@", tname)
137   - str = str:gsub("@Target@", tname:capitalize())
  136 + str = str:noun_sub("@target@", tname)
  137 + str = str:noun_sub("@Target@", tname:capitalize())
138 138 game.logSeen(who, "%s", str)
139 139 end
140 140 local known, del = false, false
... ...
... ... @@ -1179,6 +1179,10 @@ function _M:getCurrentTalentMode()
1179 1179 return self._temp_data and self._temp_data.current_talent_mode and self._temp_data.current_talent_mode[1] or "none"
1180 1180 end
1181 1181
  1182 +function _M:getCurrentTalentModeLast()
  1183 + return self._temp_data and self._temp_data.current_talent_mode and self._temp_data.current_talent_mode[#self._temp_data.current_talent_mode] or "none"
  1184 +end
  1185 +
1182 1186 function _M:setCurrentTalentMode(mode, tid)
1183 1187 if not self._temp_data then self._temp_data = {} end
1184 1188 if not self._temp_data.current_talent_mode then self._temp_data.current_talent_mode = {} end
... ...
... ... @@ -165,6 +165,13 @@ function _M:makeFrame(base, w, h, iw, ih)
165 165 f.b5 = self:getUITexture(base.."5.png")
166 166 if not w then w = iw + f.b4.w + f.b6.w end
167 167 if not h then h = ih + f.b8.h + f.b2.h end
  168 + if self.ui_conf[self.ui].specifics and self.ui_conf[self.ui].specifics[base] then
  169 + local conf = self.ui_conf[self.ui].specifics[base]
  170 + w = w + (conf.offset_w or 0)
  171 + h = h + (conf.offset_h or 0)
  172 + f.ox = conf.offset_x or 0
  173 + f.oy = conf.offset_y or 0
  174 + end
168 175 end
169 176 f.w = math.floor(w)
170 177 f.h = math.floor(h)
... ... @@ -179,8 +186,8 @@ function _M:drawFrame(f, x, y, r, g, b, a, w, h, total_w, total_h, loffset_x, lo
179 186 total_w = total_w or 0
180 187 total_h = total_h or 0
181 188
182   - x = math.floor(x)
183   - y = math.floor(y)
  189 + x = math.floor(x) + (f.ox or 0)
  190 + y = math.floor(y) + (f.oy or 0)
184 191
185 192 f.w = math.floor(w or f.w)
186 193 f.h = math.floor(h or f.h)
... ...
... ... @@ -602,10 +602,16 @@ function _M:setupUI(resizex, resizey, on_resize, addmw, addmh)
602 602 mw = mw + self.frame.ox1 - self.frame.ox2
603 603 end
604 604
605   - if on_resize then on_resize(resizex and mw or self.w, resizey and mh or self.h) end
  605 + if on_resize then
  606 + local rw, rh = on_resize(resizex and mw or self.w, resizey and mh or self.h)
  607 + if rw and rh then resizex, resizey = true, true mw, mh = rw, rh end
  608 + end
606 609 nw, nh = resizex and mw or self.w, resizey and mh or self.h
607 610 else
608   - if on_resize then on_resize(self.w, self.h) end
  611 + if on_resize then
  612 + local rw, rh = on_resize(self.w, self.h)
  613 + if rw and rh then self.w, self.h = rw, rh end
  614 + end
609 615 nw, nh = self.w, self.h
610 616 end
611 617
... ...
... ... @@ -30,6 +30,7 @@ function _M:init(t)
30 30 self.w = assert(t.width, "no entity width")
31 31 self.h = assert(t.height, "no entity height")
32 32 self.back_color = t.back_color
  33 + self.back_image = t.back_image
33 34
34 35 Base.init(self, t)
35 36 end
... ... @@ -37,6 +38,10 @@ end
37 38 function _M:generate()
38 39 self.mouse:reset()
39 40 self.key:reset()
  41 +
  42 + if self.back_image then
  43 + self.back_texture = self:getUITexture(self.back_image)
  44 + end
40 45 end
41 46
42 47 function _M:display(x, y)
... ... @@ -45,6 +50,9 @@ function _M:display(x, y)
45 50 if self.back_color then
46 51 core.display.drawQuad(x, y, self.w, self.h, unpack(self.back_color))
47 52 end
  53 + if self.back_texture then
  54 + self.back_texture.t:toScreenFull(x + (self.w - self.back_texture.w) / 2, y + (self.h - self.back_texture.h) / 2, self.back_texture.w, self.back_texture.h, self.back_texture.tw, self.back_texture.th)
  55 + end
48 56
49 57 self.entity:toScreen(nil, x, y, self.w, self.h)
50 58 end
... ...
... ... @@ -76,6 +76,20 @@ function math.triangle_area(p1, p2, p3)
76 76 return math.abs(0.5 * lu * lv * math.sin(av - au))
77 77 end
78 78
  79 +function math.find_closest_lower(x, list)
  80 + table.sort(list)
  81 + for i in ripairs_value(list) do
  82 + if x >= i then return i end
  83 + end
  84 +end
  85 +
  86 +function math.find_closest_higher(x, list)
  87 + table.sort(list)
  88 + for i in ipairs_value(list) do
  89 + if x <= i then return i end
  90 + end
  91 +end
  92 +
79 93 function lpeg.anywhere (p)
80 94 return lpeg.P{ p + 1 * lpeg.V(1) }
81 95 end
... ... @@ -134,6 +148,16 @@ function ipairs_value(t)
134 148 end
135 149 end
136 150
  151 +function ripairs_value(t)
  152 + local i = #t
  153 + return function()
  154 + if i == 0 then return nil end
  155 + local oi = i
  156 + i = i - 1
  157 + return t[oi], oi
  158 + end
  159 +end
  160 +
137 161 function table.weak_keys(t)
138 162 t = t or {}
139 163 setmetatable(t, {__mode="k"})
... ...
... ... @@ -627,7 +627,7 @@ end
627 627 function _M:createProfile(loginItem)
628 628 if not loginItem.create then
629 629 self.auth_tried = nil
630   - local d = Dialog:simpleWaiter(_t"Login in...", _t"Please wait...") core.display.forceRedraw()
  630 + local d = Dialog:simpleWaiter(_t"Logging in...", _t"Please wait...") core.display.forceRedraw()
631 631 profile:performlogin(loginItem.login, loginItem.pass)
632 632 profile:waitFirstAuth()
633 633 d:done()
... ...
... ... @@ -274,7 +274,7 @@ function _M:login()
274 274 end
275 275
276 276 function _M:loginSteam()
277   - local d = self:simpleWaiter(_t"Login...", _t"Login in your account, please wait...") core.display.forceRedraw()
  277 + local d = self:simpleWaiter(_t"Login...", _t"Logging in your account, please wait...") core.display.forceRedraw()
278 278 d:timeout(10, function() Dialog:simplePopup("Steam", _t"Steam client not found.") end)
279 279 core.steam.sessionTicket(function(ticket)
280 280 if not ticket then
... ...
... ... @@ -579,6 +579,18 @@ function _M:actBase()
579 579 end
580 580 end
581 581
  582 + for _, defs in pairs(self._applied_resources_inconstant_drain or {}) do
  583 + local new_cost = util.getval(defs.cost, self, defs.ab) or 0
  584 + new_cost = self:alterTalentCost(defs.ab, defs.res_def.drain_prop, new_cost)
  585 + if not defs.res_def.invert_values then
  586 + new_cost = - new_cost
  587 + end
  588 + if defs.old_cost ~= new_cost then
  589 + self:removeTemporaryValue(defs.res_def.regen_prop, defs.temporary_value)
  590 + defs.temporary_value = self:addTemporaryValue(defs.res_def.regen_prop, new_cost)
  591 + defs.old_cost = new_cost
  592 + end
  593 + end
582 594 self:regenResources()
583 595
584 596 -- update psionic feedback
... ... @@ -1881,6 +1893,7 @@ function _M:textRank(use_rank)
1881 1893 elseif use_rank == 3.5 then rank, color = _t"unique", "#SANDY_BROWN#"
1882 1894 elseif use_rank == 4 then rank, color = _t"boss", "#ORANGE#"
1883 1895 elseif use_rank == 5 then rank, color = _t"elite boss", "#GOLD#"
  1896 + elseif use_rank == 11 then rank, color = _t"godslayer", "#FF4000#"
1884 1897 elseif use_rank >= 10 then rank, color = _t"god", "#FF4000#"
1885 1898 end
1886 1899 return rank, color
... ... @@ -4817,6 +4830,7 @@ end
4817 4830 function _M:checkTwoHandedPenalty()
4818 4831 self:removeEffect(self.EFF_2H_PENALTY, true, true)
4819 4832 if not self:attr("allow_mainhand_2h_in_1h") then return end
  4833 + if self:attr("allow_mainhand_2h_in_1h_no_penalty") then return end
4820 4834 local mi, oi = self:getInven(self.INVEN_MAINHAND), self:getInven(self.INVEN_OFFHAND)
4821 4835 if not mi or not oi then return end
4822 4836 local mh, oh = mi[1], oi[1]
... ... @@ -5918,7 +5932,7 @@ function _M:preUseTalent(ab, silent, fake, ignore_ressources)
5918 5932 end
5919 5933 if self:fireTalentCheck("callbackOnTalentPre", ab, silent, fake, ignore_ressources) then return false end
5920 5934
5921   - if not ab.never_fail then
  5935 + if self:getCurrentTalentModeLast() ~= "forced" and not ab.never_fail then
5922 5936 -- Confused ? lose a turn!
5923 5937 if self:attr("confused") and (ab.mode ~= "sustained" or not self:isTalentActive(ab.id)) and util.getval(ab.no_energy, self, ab) ~= true and not fake and not self:attr("force_talent_ignore_ressources") then
5924 5938 if rng.percent(util.bound(self:attr("confused"), 0, 50)) then
... ... @@ -6071,6 +6085,7 @@ local sustainCallbackCheck = {
6071 6085 callbackOnPartyRemove = "talents_on_party_remove",
6072 6086 callbackOnTargeted = "talents_on_targeted",
6073 6087 callbackOnCloned = "talents_on_cloned",
  6088 + callbackOnAITalentTactics = "talents_on_ai_talent_tactics",
6074 6089 }
6075 6090 _M.sustainCallbackCheck = sustainCallbackCheck
6076 6091
... ... @@ -6347,7 +6362,7 @@ function _M:postUseTalent(ab, ret, silent)
6347 6362 trigger = true; self:incMaxFeedback(-util.getval(ab.sustain_feedback, self, ab))
6348 6363 end
6349 6364 local cost
6350   - ret._applied_costs, ret._applied_drains = {}, {} -- to store the resource effects
  6365 + ret._applied_costs, ret._applied_drains, ret._applied_inconstant_drains = {}, {}, {} -- to store the resource effects
6351 6366 for res, res_def in ipairs(_M.resources_def) do
6352 6367 -- apply sustain costs
6353 6368 cost = ab[res_def.sustain_prop]
... ... @@ -6366,7 +6381,19 @@ function _M:postUseTalent(ab, ret, silent)
6366 6381 end
6367 6382 -- apply drain costs
6368 6383 cost = ab[res_def.drain_prop]
6369   - if cost then
  6384 + if type(cost) == "function" then -- non-constant resource drain
  6385 + local init_cost = util.getval(cost, self, ab) or 0
  6386 + init_cost = self:alterTalentCost(ab, res_def.drain_prop, init_cost)
  6387 + if not res_def.invert_values then
  6388 + init_cost = - init_cost
  6389 + end
  6390 + local temporary_value = self:addTemporaryValue(res_def.regen_prop, init_cost)
  6391 + local tid_res = ab.id .. "_" .. res_def.drain_prop
  6392 + self._applied_resources_inconstant_drain = self._applied_resources_inconstant_drain or {}
  6393 + self._applied_resources_inconstant_drain[tid_res] = {ab = ab, res_def = res_def, cost = cost, old_cost = init_cost, temporary_value = temporary_value}
  6394 + ret._applied_inconstant_drains[res_def.short_name] = tid_res
  6395 + trigger = true
  6396 + elseif cost then
6370 6397 cost = util.getval(cost, self, ab) or 0
6371 6398 cost = self:alterTalentCost(ab, res_def.drain_prop, cost)
6372 6399 if cost ~= 0 then
... ... @@ -6409,6 +6436,14 @@ function _M:postUseTalent(ab, ret, silent)
6409 6436 end
6410 6437 end
6411 6438 end
  6439 + -- reverse non-constant resource drain
  6440 + if ret._applied_inconstant_drains then
  6441 + for _, tid_res in pairs(ret._applied_inconstant_drains) do
  6442 + local defs = self._applied_resources_inconstant_drain[tid_res]
  6443 + self:removeTemporaryValue(defs.res_def.regen_prop, defs.temporary_value)
  6444 + self._applied_resources_inconstant_drain[tid_res] = nil
  6445 + end
  6446 + end
6412 6447 -- reverse resource drains
6413 6448 if ret._applied_drains then
6414 6449 local res_def
... ... @@ -6479,7 +6514,7 @@ function _M:postUseTalent(ab, ret, silent)
6479 6514 end
6480 6515
6481 6516 -- break stealth, channels, etc...
6482   - if not self.turn_procs.resetting_talents then
  6517 + if self:getCurrentTalentModeLast() ~= "forced" and not self.turn_procs.resetting_talents then
6483 6518 -- Cancel stealth!
6484 6519 if not util.getval(ab.no_break_stealth, self, ab) and util.getval(ab.no_energy, self, ab) ~= true then self:breakStealth() end
6485 6520
... ... @@ -6499,11 +6534,11 @@ function _M:postUseTalent(ab, ret, silent)
6499 6534 end)
6500 6535 end
6501 6536 end
6502   - end
6503 6537
6504   - if not ab.innate and self:hasEffect(self.EFF_RAMPAGE) and ab.id ~= self.T_RAMPAGE and ab.id ~= self.T_SLAM then
6505   - local eff = self:hasEffect(self.EFF_RAMPAGE)
6506   - value = self.tempeffect_def[self.EFF_RAMPAGE].do_postUseTalent(self, eff, value)
  6538 + if not ab.innate and self:hasEffect(self.EFF_RAMPAGE) and ab.id ~= self.T_RAMPAGE and ab.id ~= self.T_SLAM then
  6539 + local eff = self:hasEffect(self.EFF_RAMPAGE)
  6540 + value = self.tempeffect_def[self.EFF_RAMPAGE].do_postUseTalent(self, eff, value)
  6541 + end
6507 6542 end
6508 6543
6509 6544 if ab.is_summon and ab.is_nature and self:attr("heal_on_nature_summon") then
... ...
... ... @@ -2087,6 +2087,7 @@ function _M:setupCommands()
2087 2087 print("===============")
2088 2088 end end,
2089 2089 [{"_g","ctrl"}] = function() if config.settings.cheat then
  2090 + package.loaded["engine.ui.Textzone"] = nil
2090 2091 package.loaded["engine.ui.Dialog"] = nil
2091 2092 package.loaded["engine.dialogs.Chat"] = nil
2092 2093 package.loaded["mod.dialogs.Chat"] = nil
... ... @@ -2094,7 +2095,7 @@ function _M:setupCommands()
2094 2095 package.loaded["engine.Chat"] = nil
2095 2096 local Chat = require "engine.Chat"
2096 2097 Chat.chat_dialog = "mod.dialogs.Chat"
2097   - local chat = Chat.new("tareyal+test", engine.Entity.new{name=_t"Imperium courrier", image="npc/undead_risen_mistress_vira.png"}, game.player)
  2098 + local chat = Chat.new("tareyal+test", engine.Entity.new{name=_t"Imperium courrier", image="talents/arcane_power.png"}, game.player)
2098 2099 chat:invoke()
2099 2100 do return end
2100 2101 DamageType:get(DamageType.ACID).projector(game.player, game.player.x, game.player.y, DamageType.ACID, 100)
... ... @@ -2222,7 +2223,7 @@ do return end
2222 2223
2223 2224 if config.settings.tome.rest_before_explore then
2224 2225 local ok = false
2225   - self.player:restInit(nil, nil, nil, function() ok = self.player.resting.rested_fully end, function() if ok then self:onTickEnd(ae) self.tick_loopback = true end end)
  2226 + self.player:restInit(nil, nil, nil, function() ok = self.player.resting and self.player.resting.rested_fully end, function() if ok then self:onTickEnd(ae) self.tick_loopback = true end end)
2226 2227 else
2227 2228 ae()
2228 2229 end
... ...
... ... @@ -203,7 +203,8 @@ function _M:tooltip(x, y)
203 203 if p.level <= data.level_range[1] - 10 then color = "CRIMSON"
204 204 elseif p.level <= data.level_range[1] - 4 then color = "ORANGE"
205 205 end
206   - tstr:add(true, {"font","bold"}, {"color", color}, _t"Min.level: "..data.level_range[1], {"color", "LAST"}, {"font","normal"}, true)
  206 + tstr:add(true, {"font","bold"}, {"color", color}, _t"Min.level: "..math.floor((data.level_range[1]+(game.state.birth.difficulty_level_add or 0))*(game.state.birth.difficulty_level_mult or 1)), {"color", "LAST"}, {"font","normal"})
  207 + tstr:add(true, {"font","bold"}, {"color", color}, _t"Max.level: "..math.ceil((data.level_range[2]+(game.state.birth.difficulty_level_add or 0))*(game.state.birth.difficulty_level_mult or 1)), {"color", "LAST"}, {"font","normal"}, true)
207 208 end
208 209 end
209 210 end
... ...
... ... @@ -2482,7 +2482,7 @@ function _M:on_prepickup(who, idx)
2482 2482 end
2483 2483 if who.player and self.force_lore_artifact then
2484 2484 game.party:additionalLore(self.unique, self:getName(), "artifacts", self.desc)
2485   - game.party:learnLore(self.unique)
  2485 + game.party:learnLore(self.unique, false, false, false, nil, self)
2486 2486 end
2487 2487 end
2488 2488
... ... @@ -2501,7 +2501,7 @@ function _M:on_identify()
2501 2501 end
2502 2502 if self.unique and self.desc and not self.no_unique_lore then
2503 2503 game.party:additionalLore(self.unique, self:getName{no_add_name=true, do_color=false, no_count=true}, "artifacts", self.desc)
2504   - game.party:learnLore(self.unique, false, false, true)
  2504 + game.party:learnLore(self.unique, false, false, true, nil, self)
2505 2505 end
2506 2506 end)
2507 2507 end
... ...
... ... @@ -1386,6 +1386,10 @@ function _M:aiTalentTactics(t, aitarget, target_list, tactic, tg, wt_mod)
1386 1386 end --(DEBUGGING transitional)
1387 1387 if type(tactical) == "function" then tactical = tactical(self, t, aitarget) end
1388 1388 end
  1389 + local hd = {"ActorAI:aiTalentTactics", tactical=table.clone(tactical, true), t=t, ai_target=ai_target}
  1390 + self:triggerHook(hd)
  1391 + self:fireTalentCheck("callbackOnAITalentTactics", hd)
  1392 + tactical = hd.tactical
1389 1393 if log_detail >= 2 then print("[aiTalentTactics]__ using talent tactical table for", t.id) table.print(tactical, "\t___") end
1390 1394 if not tactical then return false end
1391 1395
... ... @@ -1568,7 +1572,7 @@ function _M:aiTalentTactics(t, aitarget, target_list, tactic, tg, wt_mod)
1568 1572 else
1569 1573 val_type, val_wt = next(val, val_type) if not val_wt then break end
1570 1574 end
1571   - if act == self and self:attr("encased_in_ice") and tact == "attack" or tact == "attackarea" then
  1575 + if act == self and self:attr("encased_in_ice") and (tact == "attack" or tact == "attackarea") then
1572 1576 weight = s_mult * math.abs(benefit) -- Frozen status ignores selffire and allows self-fire
1573 1577 elseif act == self then -- hit self
1574 1578 weight = selffire*friendlyfire*s_mult*benefit -- matches actor:project
... ...
... ... @@ -612,14 +612,10 @@ local function archery_projectile(tx, ty, tg, self, tmp)
612 612 end
613 613
614 614 -- Siege Arrows
615   - if hitted and ammo and ammo.siege_impact and (not self.shattering_impact_last_turn or self.shattering_impact_last_turn < game.turn) then
  615 + if hitted and ammo and ammo.siege_impact then
616 616 local dam = dam * ammo.siege_impact
617   - local invuln = target.invulnerable
618   - game.logSeen(target, "The shattering blow creates a shockwave!")
619   - target.invulnerable = 1 -- Target already hit, don't damage it twice
620   - self:project({type="ball", radius=1, friendlyfire=false}, target.x, target.y, DamageType.PHYSICAL, dam)
621   - target.invulnerable = invuln
622   - self.shattering_impact_last_turn = game.turn
  617 + game.logSeen(target, "The siege arrow creates a shockwave!")
  618 + self:project({type="ball", radius=1, friendlyfire=false, act_exclude={[target.uid]=true}}, target.x, target.y, DamageType.PHYSICAL, dam)
623 619 end
624 620
625 621 if self ~= target then
... ...
... ... @@ -127,7 +127,7 @@ function _M:attackTarget(target, damtype, mult, noenergy, force_unarmed)
127 127 -- if ret then return false end
128 128 -- end
129 129
130   - if not target.turn_procs.warding_weapon and target:knowTalent(target.T_WARDING_WEAPON) and target:getTalentLevelRaw(target.T_WARDING_WEAPON) >= 5
  130 + if not target.turn_procs.warding_weapon and target:knowTalent(target.T_WARDING_WEAPON) and target:getTalentLevel(target.T_WARDING_WEAPON) >= 5
131 131 and rng.percent(target:callTalent(target.T_WARDING_WEAPON, "getChance")) then
132 132 local t = self:getTalentFromId(self.T_WARDING_WEAPON)
133 133 if target:getPsi() >= t.psi then
... ... @@ -1389,6 +1389,7 @@ function _M:combatAttackRanged(weapon, ammo)
1389 1389 local stats
1390 1390 if self:attr("use_psi_combat") then stats = (self:getCun(100, true) - 10) * (0.6 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
1391 1391 elseif weapon and weapon.wil_attack then stats = self:getWil(100, true) - 10
  1392 + elseif weapon and weapon.mag_attack then stats = self:getMag(100, true) - 10
1392 1393 else stats = self:getDex(100, true) - 10
1393 1394 end
1394 1395 local d = self:combatAttackBase(weapon, ammo) + stats + (self.combat_atk_ranged or 0)
... ...
... ... @@ -108,7 +108,8 @@ function _M:relearningLore(v)
108 108 self.relearning_lore = v
109 109 end
110 110
111   -function _M:learnLore(lore, nopopup, silent, nostop, after_learn_cb)
  111 +function _M:learnLore(lore, nopopup, silent, nostop, after_learn_cb, display_entity)
  112 + print("[LORE] learning..", lore)
112 113 local l = self:getLore(lore, silent)
113 114 if not l then return end
114 115 local learnt = false
... ... @@ -123,7 +124,7 @@ function _M:learnLore(lore, nopopup, silent, nostop, after_learn_cb)
123 124 if not self:knownLore(lore) or l.always_pop then
124 125 game.logPlayer(self, "Lore found: #0080FF#%s", l.name)
125 126 if not nopopup then
126   - LorePopup.new(l, game.w * 0.6, 0.8, after_learn_cb)
  127 + LorePopup.new(l, game.w * 0.6, 0.8, after_learn_cb, display_entity)
127 128 game.logPlayer(self, "You can read all your collected lore in the game menu, by pressing Escape.")
128 129 end
129 130 learnt = true
... ...
... ... @@ -117,6 +117,21 @@ newAchievement{
117 117 desc = _t[[Won ToME by closing the Void portal and letting yourself be killed by Aeryn to prevent the Way to enslave every sentient being on Eyal.]],
118 118 }
119 119 newAchievement{
  120 + name = "This is how the world ends: swallowed in fire, but not in darkness.", id = "AOADS_BURN",
  121 + show = "name", huge=true,
  122 + desc = _t[["Won" ToME by sacrificing yourself for your patron Distant Sun, opening a portal for it to burn and consume the world.]],
  123 +}
  124 +newAchievement{
  125 + name = "Last Instant of Sanity", id = "AOADS_SELFLESS",
  126 + show = "name", huge=true,
  127 + desc = _t[[Won ToME by closing the Void portal and letting yourself be killed by Aeryn to prevent your mad patron sun from burning the world in a searing flash.]],
  128 +}
  129 +newAchievement{
  130 + name = "They Came Back For Eyal", id = "AOADS_SHERTUL",
  131 + show = "name", huge=true,
  132 + desc = _t[[Won ToME thanks to a Sher'tul stopping you at the last moment from opening a portal to your mad patron sun.]],
  133 +}
  134 +newAchievement{
120 135 name = "Tactical master", id = "SORCERER_NO_PORTAL",
121 136 show = "name", huge=true,
122 137 desc = _t[[Fought the two Sorcerers without closing any invocation portals.]],
... ...
... ... @@ -123,7 +123,11 @@ newBirthDescriptor{
123 123 resolvers.inventorybirth{ id=true,
124 124 {type="weapon", subtype="greatsword", name="iron greatsword", autoreq=true, ego_chance= -1000},
125 125 },
126   -
  126 + resolvers.generic(function(self)
  127 + if not profile.mod.allow_build.paladin_avatar then
  128 + self:learnTalent(self.T_AVATAR_DISTANT_SUN_UNLOCK_CHECKER, true)
  129 + end
  130 + end),
127 131 },
128 132 copy_add = {
129 133 life_rating = 2,
... ...
... ... @@ -161,6 +161,7 @@ newBirthDescriptor
161 161 confusion_immune = 0.35,
162 162 max_air = 200,
163 163 moddable_tile = "yeek",
  164 + moddable_tile_nude = 1,
164 165 random_name_def = "yeek_#sex#",
165 166 },
166 167 experience = 0.85,
... ...
  1 +{
  2 + "24": {
  3 + "id": 24,
  4 + "name": "lua-code",
  5 + "data": {
  6 + "code": "player:callTalent(player.T_AVATAR_DISTANT_SUN_UNLOCK_CHECKER, \"doUnlock\")"
  7 + },
  8 + "class": "lua-code",
  9 + "html": "lua-code",
  10 + "typenode": true,
  11 + "inputs": {
  12 + "input_1": {
  13 + "connections": [
  14 + {
  15 + "node": "26",
  16 + "input": "output_1"
  17 + },
  18 + {
  19 + "node": "32",
  20 + "input": "output_1"
  21 + }
  22 + ]
  23 + }
  24 + },
  25 + "outputs": {
  26 + "output_1": {
  27 + "connections": []
  28 + }
  29 + },
  30 + "pos_x": 2440,
  31 + "pos_y": 549.1111111111111
  32 + },
  33 + "25": {
  34 + "id": 25,
  35 + "name": "chat",
  36 + "data": {
  37 + "chatid": "welcome",
  38 + "chat": "<<<You feel a gentle warmth in your mind. Something speaks directly to your mind!>>>\n#YELLOW#HELLO FRIEND. I AM A STAR FROM FAR AWAY. I HAVE HEARD YOU PRAISING YOUR SUN. WHY NOT PRAISE ME INSTEAD? I AM A MUCH BETTER SUN THAN THAT DULL, LIFELESS GAS ORB IN YOUR VICINITY. PLEASE, ALLOW ME TO DEMONSTRATE MY GOOD INTENTIONS.",
  39 + "answer1": "Uhh, okay. Sure. Mind showing me?",
  40 + "answer2": "I don't take unsolicited messages from... stars."
  41 + },
  42 + "class": "chat",
  43 + "html": "chat2",
  44 + "typenode": true,
  45 + "inputs": {
  46 + "input_1": {
  47 + "connections": []
  48 + }
  49 + },
  50 + "outputs": {
  51 + "output_1": {
  52 + "connections": [
  53 + {
  54 + "node": "26",
  55 + "output": "input_1"
  56 + }
  57 + ]
  58 + },
  59 + "output_2": {
  60 + "connections": [
  61 + {
  62 + "node": "29",
  63 + "output": "input_1"
  64 + }
  65 + ]
  66 + }
  67 + },
  68 + "pos_x": 23,
  69 + "pos_y": 167
  70 + },
  71 + "26": {
  72 + "id": 26,
  73 + "name": "chat",
  74 + "data": {
  75 + "chatid": "chat2",
  76 + "chat": "#YELLOW#YOU ONLY HAD TO ASK, MY FRIEND.",
  77 + "answer1": "#CRIMSON#[The area around you erupts in flames, burning your foes]"
  78 + },
  79 + "class": "chat",
  80 + "html": "chat1",
  81 + "typenode": true,
  82 + "inputs": {
  83 + "input_1": {
  84 + "connections": [
  85 + {
  86 + "node": "25",
  87 + "input": "output_1"
  88 + },
  89 + {
  90 + "node": "30",
  91 + "input": "output_1"
  92 + },
  93 + {
  94 + "node": "31",
  95 + "input": "output_1"
  96 + }
  97 + ]
  98 + }
  99 + },
  100 + "outputs": {
  101 + "output_1": {
  102 + "connections": [
  103 + {
  104 + "node": "24",
  105 + "output": "input_1"
  106 + }
  107 + ]
  108 + }
  109 + },
  110 + "pos_x": 2084.3333333333335,
  111 + "pos_y": 265
  112 + },
  113 + "29": {
  114 + "id": 29,
  115 + "name": "chat",
  116 + "data": {
  117 + "chatid": "chat3",
  118 + "chat": "#YELLOW#DON'T BE LIKE THAT. I KNOW YOU ARE ON A NOBLE QUEST.",
  119 + "answer1": "Look, I appreciate the offer, but I don't know you.",
  120 + "answer2": "I'm busy at the moment, so maybe call back another time?",
  121 + "answer3": "If you say so."
  122 + },
  123 + "class": "chat",
  124 + "html": "chat3",
  125 + "typenode": true,
  126 + "inputs": {
  127 + "input_1": {
  128 + "connections": [
  129 + {
  130 + "node": "25",
  131 + "input": "output_2"
  132 + }
  133 + ]
  134 + }
  135 + },
  136 + "outputs": {
  137 + "output_1": {
  138 + "connections": [
  139 + {
  140 + "node": "30",
  141 + "output": "input_1"
  142 + }
  143 + ]
  144 + },
  145 + "output_2": {
  146 + "connections": [
  147 + {
  148 + "node": "31",
  149 + "output": "input_1"
  150 + }
  151 + ]
  152 + },
  153 + "output_3": {
  154 + "connections": [
  155 + {
  156 + "node": "32",
  157 + "output": "input_1"
  158 + }
  159 + ]
  160 + }
  161 + },
  162 + "pos_x": 670,
  163 + "pos_y": 631
  164 + },
  165 + "30": {
  166 + "id": 30,
  167 + "name": "chat",
  168 + "data": {
  169 + "chatid": "chat4",
  170 + "chat": "#YELLOW#I AM YOUR FRIEND. NOW YOU KNOW ME. LET US BEGIN A LONG AND FRUITFUL FRIENDSHIP.",
  171 + "answer1": "Uhh, okay. Sure. Mind showing me?"
  172 + },
  173 + "class": "chat",
  174 + "html": "chat1",
  175 + "typenode": true,
  176 + "inputs": {
  177 + "input_1": {
  178 + "connections": [
  179 + {
  180 + "node": "29",
  181 + "input": "output_1"
  182 + }
  183 + ]
  184 + }
  185 + },
  186 + "outputs": {
  187 + "output_1": {
  188 + "connections": [
  189 + {
  190 + "node": "26",
  191 + "output": "input_1"
  192 + }
  193 + ]
  194 + }
  195 + },
  196 + "pos_x": 1431,
  197 + "pos_y": 388
  198 + },
  199 + "31": {
  200 + "id": 31,
  201 + "name": "chat",
  202 + "data": {
  203 + "chatid": "chat5",
  204 + "chat": "#YELLOW#BUSY, YOU SAY? I CAN FIX THAT, LIKE ANY GOOD FRIEND WOULD.",
  205 + "answer1": "Uhh, okay. Sure. Mind showing me?"
  206 + },
  207 + "class": "chat",
  208 + "html": "chat1",
  209 + "typenode": true,
  210 + "inputs": {
  211 + "input_1": {
  212 + "connections": [
  213 + {
  214 + "node": "29",
  215 + "input": "output_2"
  216 + }
  217 + ]
  218 + }
  219 + },
  220 + "outputs": {
  221 + "output_1": {
  222 + "connections": [
  223 + {
  224 + "node": "26",
  225 + "output": "input_1"
  226 + }
  227 + ]
  228 + }
  229 + },
  230 + "pos_x": 1429,
  231 + "pos_y": 640
  232 + },
  233 + "32": {
  234 + "id": 32,
  235 + "name": "chat",
  236 + "data": {
  237 + "chatid": "chat6",
  238 + "chat": "#YELLOW#YOU DON'T BELIEVE ME? THEN PLEASE, ALLOW ME TO SHOW YOU A MERE FRACTION OF MY POWER.",
  239 + "answer1": "#CRIMSON#[The area around you erupts in flames, burning your foes]"
  240 + },
  241 + "class": "chat",
  242 + "html": "chat1",
  243 + "typenode": true,
  244 + "inputs": {
  245 + "input_1": {
  246 + "connections": [
  247 + {
  248 + "node": "29",
  249 + "input": "output_3"
  250 + }
  251 + ]
  252 + }
  253 + },
  254 + "outputs": {
  255 + "output_1": {
  256 + "connections": [
  257 + {
  258 + "node": "24",
  259 + "output": "input_1"
  260 + }
  261 + ]
  262 + }
  263 + },
  264 + "pos_x": 1420,
  265 + "pos_y": 903
  266 + }
  267 +}
\ No newline at end of file
... ...
  1 +{
  2 + "25": {
  3 + "id": 25,
  4 + "name": "chat",
  5 + "data": {
  6 + "chatid": "chat4",
  7 + "chat": "#YELLOW#YOU ARE A CURIOUS ONE.",
  8 + "answer1": "[...]"
  9 + },
  10 + "class": "chat",
  11 + "html": "chat1",
  12 + "typenode": true,
  13 + "inputs": {
  14 + "input_1": {
  15 + "connections": [
  16 + {
  17 + "node": "32",
  18 + "input": "output_1"
  19 + }
  20 + ]
  21 + }
  22 + },
  23 + "outputs": {
  24 + "output_1": {
  25 + "connections": [
  26 + {
  27 + "node": "32",
  28 + "output": "input_1"
  29 + }
  30 + ]
  31 + }
  32 + },
  33 + "pos_x": 1564,
  34 + "pos_y": 207
  35 + },
  36 + "27": {
  37 + "id": 27,
  38 + "name": "chat",
  39 + "data": {
  40 + "chatid": "chat6",
  41 + "chat": "#YELLOW#YOU HAVE MADE THE RIGHT CHOICE. TOGETHER, WE SHALL BRING ABOUT THE DESTRUCTION OF OUR ENEMIES.",
  42 + "answer1": "#GOLD#[you are now an Avatar of a Distant Sun]"
  43 + },
  44 + "class": "chat",
  45 + "html": "chat1",
  46 + "typenode": true,
  47 + "inputs": {
  48 + "input_1": {
  49 + "connections": [
  50 + {
  51 + "node": "29",
  52 + "input": "output_1"
  53 + },
  54 + {
  55 + "node": "36",
  56 + "input": "output_1"
  57 + }
  58 + ]
  59 + }
  60 + },
  61 + "outputs": {
  62 + "output_1": {
  63 + "connections": [
  64 + {
  65 + "node": "39",
  66 + "output": "input_1"
  67 + }
  68 + ]
  69 + }
  70 + },
  71 + "pos_x": 2507,
  72 + "pos_y": -110
  73 + },
  74 + "29": {
  75 + "id": 29,
  76 + "name": "chat",
  77 + "data": {
  78 + "chatid": "welcome",
  79 + "chat": "<<<You feel the gentle warmth of your Distant Sun patron. It speaks directly to your mind!>>>\n#YELLOW#I AM HERE. DO YOU DESIRE TO SMITE EVIL, DESTROY THE DARKNESS AND SCOUR THE EARTH? I SHALL AID YOU IN THIS QUEST. TOGETHER, WE WILL BE UNSTOPPABLE. ALL DARKNESS SHALL BE CONSUMED BY OUR LIGHT.\n#LAST#",
  80 + "answer1": "Yes, give me your power!",
  81 + "answer2": "Who... Who are you?"
  82 + },
  83 + "class": "chat",
  84 + "html": "chat2",
  85 + "typenode": true,
  86 + "inputs": {
  87 + "input_1": {
  88 + "connections": []
  89 + }
  90 + },
  91 + "outputs": {
  92 + "output_1": {
  93 + "connections": [
  94 + {
  95 + "node": "27",
  96 + "output": "input_1"
  97 + }
  98 + ]
  99 + },
  100 + "output_2": {
  101 + "connections": [
  102 + {
  103 + "node": "32",
  104 + "output": "input_1"
  105 + }
  106 + ]
  107 + }
  108 + },
  109 + "pos_x": 92,
  110 + "pos_y": 131
  111 + },
  112 + "32": {
  113 + "id": 32,
  114 + "name": "chat",
  115 + "data": {
  116 + "chatid": "chat7",
  117 + "chat": "#YELLOW#I AM YOUR FRIEND. IT IS GOOD TO HAVE FRIENDS, ISN'T IT?",
  118 + "answer1": "But how are you speaking to me?",
  119 + "answer2": "You still haven't told me who or what you are.",
  120 + "answer3": "But what are you getting out of this?",
  121 + "answer4": "That doesn't tell me anything."
  122 + },
  123 + "class": "chat",
  124 + "html": "chat4",
  125 + "typenode": true,
  126 + "inputs": {
  127 + "input_1": {
  128 + "connections": [
  129 + {
  130 + "node": "29",
  131 + "input": "output_2"
  132 + },
  133 + {
  134 + "node": "25",
  135 + "input": "output_1"
  136 + },
  137 + {
  138 + "node": "33",
  139 + "input": "output_1"
  140 + },
  141 + {
  142 + "node": "34",
  143 + "input": "output_1"
  144 + }
  145 + ]
  146 + }
  147 + },
  148 + "outputs": {
  149 + "output_1": {
  150 + "connections": [
  151 + {
  152 + "node": "25",
  153 + "output": "input_1"
  154 + }
  155 + ]
  156 + },
  157 + "output_2": {
  158 + "connections": [
  159 + {
  160 + "node": "33",
  161 + "output": "input_1"
  162 + }
  163 + ]
  164 + },
  165 + "output_3": {
  166 + "connections": [
  167 + {
  168 + "node": "34",
  169 + "output": "input_1"
  170 + }
  171 + ]
  172 + },
  173 + "output_4": {
  174 + "connections": [
  175 + {
  176 + "node": "36",
  177 + "output": "input_1"
  178 + }
  179 + ]
  180 + }
  181 + },
  182 + "pos_x": 721,
  183 + "pos_y": 330
  184 + },
  185 + "33": {
  186 + "id": 33,
  187 + "name": "chat",
  188 + "data": {
  189 + "chatid": "chat8",
  190 + "chat": "#YELLOW#YOU NEED NOT CONCERN YOURSELF WITH SUCH THINGS. I KNOW YOU CRAVE POWER. I KNOW THE WEIGHT OF THE WORLD IS ON YOUR SHOULDERS. SO, ACCEPT MY BOONS. ALLOW ME TO HELP YOU.\n",
  191 + "answer1": "[...]"
  192 + },
  193 + "class": "chat",
  194 + "html": "chat1",
  195 + "typenode": true,
  196 + "inputs": {
  197 + "input_1": {
  198 + "connections": [
  199 + {
  200 + "node": "32",
  201 + "input": "output_2"
  202 + }
  203 + ]
  204 + }
  205 + },
  206 + "outputs": {
  207 + "output_1": {
  208 + "connections": [
  209 + {
  210 + "node": "32",
  211 + "output": "input_1"
  212 + }
  213 + ]
  214 + }
  215 + },
  216 + "pos_x": 1558,
  217 + "pos_y": 415
  218 + },
  219 + "34": {
  220 + "id": 34,
  221 + "name": "chat",
  222 + "data": {
  223 + "chatid": "chat9",
  224 + "chat": "#YELLOW#I AM GETTING PLENTY OUT OF THIS.",
  225 + "answer1": "[...]"
  226 + },
  227 + "class": "chat",
  228 + "html": "chat1",
  229 + "typenode": true,
  230 + "inputs": {
  231 + "input_1": {
  232 + "connections": [
  233 + {
  234 + "node": "32",
  235 + "input": "output_3"
  236 + }
  237 + ]
  238 + }
  239 + },
  240 + "outputs": {
  241 + "output_1": {
  242 + "connections": [
  243 + {
  244 + "node": "32",
  245 + "output": "input_1"
  246 + }
  247 + ]
  248 + }
  249 + },
  250 + "pos_x": 1559,
  251 + "pos_y": 776
  252 + },
  253 + "36": {
  254 + "id": 36,
  255 + "name": "chat",
  256 + "data": {
  257 + "chatid": "chat10",
  258 + "chat": "#YELLOW#I TIRE OF YOUR NAGGING QUESTIONS. TALK, TALK, TALK. YOU HAVE A SIMPLE CHOICE BEFORE YOU. WILL YOU BECOME POWERFUL? OR WILL YOU BE WEAK AND ALONE?",
  259 + "answer1": "Yes, give me your power!",
  260 + "answer2": "I don't trust you. Please go away."
  261 + },
  262 + "class": "chat",
  263 + "html": "chat2",
  264 + "typenode": true,
  265 + "inputs": {
  266 + "input_1": {
  267 + "connections": [
  268 + {
  269 + "node": "32",
  270 + "input": "output_4"
  271 + }
  272 + ]
  273 + }
  274 + },
  275 + "outputs": {
  276 + "output_1": {
  277 + "connections": [
  278 + {
  279 + "node": "27",
  280 + "output": "input_1"
  281 + }
  282 + ]
  283 + },
  284 + "output_2": {
  285 + "connections": [
  286 + {
  287 + "node": "37",
  288 + "output": "input_1"
  289 + }
  290 + ]
  291 + }
  292 + },
  293 + "pos_x": 1557,
  294 + "pos_y": 1046
  295 + },
  296 + "37": {
  297 + "id": 37,
  298 + "name": "chat",
  299 + "data": {
  300 + "chatid": "chat11",
  301 + "chat": "#YELLOW#I HAVE LITTLE PATIENCE FOR TIME WASTERS. THIS SHALL BE THE LAST TIME WE SPEAK.",
  302 + "answer1": "#GRAY#[prodigy point refunded]"
  303 + },
  304 + "class": "chat",
  305 + "html": "chat1",
  306 + "typenode": true,
  307 + "inputs": {
  308 + "input_1": {
  309 + "connections": [
  310 + {
  311 + "node": "36",
  312 + "input": "output_2"
  313 + }
  314 + ]
  315 + }
  316 + },
  317 + "outputs": {
  318 + "output_1": {
  319 + "connections": [
  320 + {
  321 + "node": "40",
  322 + "output": "input_1"
  323 + }
  324 + ]
  325 + }
  326 + },
  327 + "pos_x": 2017,
  328 + "pos_y": 1318
  329 + },
  330 + "39": {
  331 + "id": 39,
  332 + "name": "lua-code",
  333 + "data": {
  334 + "code": "player:callTalent(player.T_AVATAR_OF_A_DISTANT_SUN, \"becomeAvatar\")"
  335 + },
  336 + "class": "lua-code",
  337 + "html": "lua-code",
  338 + "typenode": true,
  339 + "inputs": {
  340 + "input_1": {
  341 + "connections": [
  342 + {
  343 + "node": "27",
  344 + "input": "output_1"
  345 + }
  346 + ]
  347 + }
  348 + },
  349 + "outputs": {
  350 + "output_1": {
  351 + "connections": []
  352 + }
  353 + },
  354 + "pos_x": 2859.4285714285716,
  355 + "pos_y": -43.42857142857143
  356 + },
  357 + "40": {
  358 + "id": 40,
  359 + "name": "lua-code",
  360 + "data": {
  361 + "code": "player:unlearnTalent(player.T_AVATAR_OF_A_DISTANT_SUN)\nplayer.unused_prodigies = player.unused_prodigies + 1\nplayer:attr(\"pissed_of_distant_sun\", 1)"
  362 + },
  363 + "class": "lua-code",
  364 + "html": "lua-code",
  365 + "typenode": true,
  366 + "inputs": {
  367 + "input_1": {
  368 + "connections": [
  369 + {
  370 + "node": "37",
  371 + "input": "output_1"
  372 + }
  373 + ]
  374 + }
  375 + },
  376 + "outputs": {
  377 + "output_1": {
  378 + "connections": []
  379 + }
  380 + },
  381 + "pos_x": 2477,
  382 + "pos_y": 1329
  383 + }
  384 +}
\ No newline at end of file
... ...
... ... @@ -17,13 +17,14 @@
17 17 -- Nicolas Casalini "DarkGod"
18 18 -- darkgod@te4.org
19 19
20   -
21   -
22 20 local Dialog = require "engine.ui.Dialog"
23 21 local DamageType = require "engine.DamageType"
24 22 local o = version
25 23 local src = game.player
26 24
  25 +-- If staff does not provide a portrait, use the talent
  26 +if not npc.has_command_staff_portrait then cur_chat.npc_force_display_entity = src:getTalentFromId(src.T_COMMAND_STAFF) end
  27 +
27 28 if o.factory_settings then
28 29 print("Just started the chat, and we apparently have o.factory_settings.")
29 30 else
... ...
... ... @@ -19,6 +19,8 @@
19 19 local q = game.player:hasQuest("lost-merchant")
20 20 if q and q:isStatus(q.COMPLETED, "saved") then
21 21
  22 +npc.chat_display_entity = engine.Entity.new{name=_t"Urthol's Wondrous Emporium", image="portrait/shop_urthol_s_wondrous_emporium.png"}
  23 +
22 24 local p = game:getPlayer(true)
23 25
24 26 local trap = p:knowTalentType("cunning/trapping") and not game.state:unlockTalentCheck(player.T_AMBUSH_TRAP, player)
... ...
... ... @@ -21,6 +21,7 @@ local p = game.party:findMember{main=true}
21 21
22 22 local function void_portal_open(npc, player)
23 23 -- Charred scar was successful
  24 + -- do return false end
24 25 if player:hasQuest("charred-scar") and player:hasQuest("charred-scar"):isCompleted("stopped") then return false end
25 26 return true
26 27 end
... ... @@ -32,6 +33,100 @@ end
32 33
33 34
34 35 --------------------------------------------------------
  36 +-- Distant Sun is not exactly benevolent after all