diff --git a/game/engine/Actor.lua b/game/engine/Actor.lua index 812635a84bff6c4c7a6363399dab61c2efd933b1..3c5b499ace3eb34c397743c31c0ef50b7ee2caf0 100644 --- a/game/engine/Actor.lua +++ b/game/engine/Actor.lua @@ -270,6 +270,22 @@ function _M:canSee(actor, def, def_pct) return true, 100 end +--- Does the actor have LOS to the target +function _M:hasLOS(x, y) + local l = line.new(self.x, self.y, x, y) + local lx, ly = l() + while lx and ly do + if game.level.map:checkAllEntities(lx, ly, "block_sight") then break end + + lx, ly = l() + end + -- Ok if we are at the end reset lx and ly for the next code + if not lx and not ly then lx, ly = x, y end + + if lx == x and ly == y then return true, lx, ly end + return false, lx, ly +end + local function gettable(base, name) for w in name:gmatch("[^.]+") do base = base[w] diff --git a/game/engine/ai/talented.lua b/game/engine/ai/talented.lua index 91b6d855dc0cb2aa88747ba58c0c4c1bf1ae1161..246c41fe029109e697814a34934c9d3116696f99 100644 --- a/game/engine/ai/talented.lua +++ b/game/engine/ai/talented.lua @@ -27,7 +27,7 @@ newAI("dumb_talented", function(self) for tid, _ in pairs(self.talents) do local t = self:getTalentFromId(tid) -- print(self.name, self.uid, "dumb ai talents can try use", t.name, tid, "::", t.mode, not self:isTalentCoolingDown(t), target_dist <= self:getTalentRange(t), self:preUseTalent(t, true), self:canProject({type="bolt"}, self.ai_target.actor.x, self.ai_target.actor.y)) - if t.mode == "activated" and not self:isTalentCoolingDown(t) and target_dist <= self:getTalentRange(t) and self:preUseTalent(t, true) and self:canProject({type="bolt"}, self.ai_target.actor.x, self.ai_target.actor.y) then + if t.mode == "activated" and not self:isTalentCoolingDown(t) and target_dist <= self:getTalentRange(t) and self:preUseTalent(t, true) and self:canProject({type="bolt"}, self.ai_target.actor.x, self.ai_target.actor.y) and self:hasLOS(self.ai_target.actor.x, self.ai_target.actor.y) then avail[#avail+1] = tid print(self.name, self.uid, "dumb ai talents can use", t.name, tid) elseif t.mode == "sustained" and not self:isTalentCoolingDown(t) and not self:isTalentActive(t.id) and self:preUseTalent(t, true) then diff --git a/game/modules/tome/data/birth/classes/mage.lua b/game/modules/tome/data/birth/classes/mage.lua index db04d071b7c4643f926be86ed6cd6181d6d899fa..a7647fc5ed8906d9c00db82b5f9fbeb0c19a2ec1 100644 --- a/game/modules/tome/data/birth/classes/mage.lua +++ b/game/modules/tome/data/birth/classes/mage.lua @@ -104,12 +104,12 @@ newBirthDescriptor{ }, stats = { mag=5, dex=3, wil=1, }, talents_types = { - ["spell/alchemy"]={true, 0.3}, + ["spell/explosives"]={true, 0.3}, ["spell/infusion"]={true, 0.3}, ["spell/golemancy"]={true, 0.3}, ["spell/advanced-golemancy"]={false, 0.3}, - ["spell/gemology"]={true, 0.3}, - ["spell/herbalism"]={false, 0.3}, + ["spell/stone-alchemy"]={true, 0.3}, + ["spell/fire-alchemy"]={false, 0.3}, ["spell/staff-combat"]={true, 0.3}, ["cunning/survival"]={false, -0.1}, ["technique/combat-training"]={false, 0}, diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua index 464ef448294bf4f48306ca2e9939e16a86024fb4..a796f4973e9317324118db9ef18cb312d787c318 100644 --- a/game/modules/tome/data/damage_types.lua +++ b/game/modules/tome/data/damage_types.lua @@ -42,7 +42,8 @@ setDefaultProjector(function(src, x, y, type, dam) -- Reduce damage with resistance if target.resists then - local pen = (src.resists_pen.all or 0) + (src.resists_pen[type] or 0) + local pen = 0 + if src.resists_pen then pen = (src.resists_pen.all or 0) + (src.resists_pen[type] or 0) end local res = (target.resists.all or 0) + (target.resists[type] or 0) res = res * (100 - pen) / 100 print("[PROJECTOR] res", res, (100 - res) / 100, " on dam", dam) diff --git a/game/modules/tome/data/general/npcs/snow-giant.lua b/game/modules/tome/data/general/npcs/snow-giant.lua index ae046bd2b248127d5ffed24e6058368b8434b947..4ae538587eb17304503f14acb9eb1b542e34ceb4 100644 --- a/game/modules/tome/data/general/npcs/snow-giant.lua +++ b/game/modules/tome/data/general/npcs/snow-giant.lua @@ -40,7 +40,7 @@ newEntity{ energy = { mod=1 }, stats = { str=20, dex=8, mag=6, con=16 }, - resists = { [DamageType.PHYSICAL] = 20, [DamageType.FIRE] = 50, }, + resists = { [DamageType.PHYSICAL] = 20, [DamageType.COLD] = 50, }, no_breath = 1, confusion_immune = 1, diff --git a/game/modules/tome/data/talents/spells/advanced-golemancy.lua b/game/modules/tome/data/talents/spells/advanced-golemancy.lua index 2d7ad2f196215f29a713e12d3722b16dd529f580..4a373eb269d88c3d20f1c99397eba86baf2d5170 100644 --- a/game/modules/tome/data/talents/spells/advanced-golemancy.lua +++ b/game/modules/tome/data/talents/spells/advanced-golemancy.lua @@ -28,16 +28,12 @@ newTalent{ self.alchemy_golem:learnTalent(self.T_SWORD_MASTERY, true) self.alchemy_golem:learnTalent(self.T_MACE_MASTERY, true) self.alchemy_golem:learnTalent(self.T_AXE_MASTERY, true) - self.alchemy_golem:learnTalent(self.T_HEAVY_ARMOUR_TRAINING, true) - self.alchemy_golem:learnTalent(self.T_MASSIVE_ARMOUR_TRAINING, true) end, on_unlearn = function(self, t) self.alchemy_golem:unlearnTalent(self.T_WEAPON_COMBAT, true) self.alchemy_golem:unlearnTalent(self.T_SWORD_MASTERY, true) self.alchemy_golem:unlearnTalent(self.T_MACE_MASTERY, true) self.alchemy_golem:unlearnTalent(self.T_AXE_MASTERY, true) - self.alchemy_golem:unlearnTalent(self.T_HEAVY_ARMOUR_TRAINING, true) - self.alchemy_golem:unlearnTalent(self.T_MASSIVE_ARMOUR_TRAINING, true) end, info = function(self, t) return ([[Improves your golem proficiency with two handed weapons.]]) diff --git a/game/modules/tome/data/talents/spells/alchemy.lua b/game/modules/tome/data/talents/spells/explosives.lua similarity index 68% rename from game/modules/tome/data/talents/spells/alchemy.lua rename to game/modules/tome/data/talents/spells/explosives.lua index 6a905325dc39ec588e12db8a1d5f547fb14353cc..f195113427b6c5541afad71b69f076129ee74a05 100644 --- a/game/modules/tome/data/talents/spells/alchemy.lua +++ b/game/modules/tome/data/talents/spells/explosives.lua @@ -109,6 +109,7 @@ newTalent{ if ammo then dam, damtype = t.computeDamage(self, t, ammo) end return ([[Imbue an alchemist gem with an explosive charge of mana and throw it. The gem will explode for %0.2f %s damage. + Each kind of gem will also provide a specific effect. The damage will improve with better gems and Magic stat and the range with your dexterity.]]):format(dam, DamageType:get(damtype).name) end, } @@ -149,38 +150,82 @@ newTalent{ } newTalent{ - name = "Stone Touch", + name = "Shockwave Bomb", type = {"spell/explosives",4}, require = spells_req4, points = 5, - mana = 80, - cooldown = 15, + mana = 40, + cooldown = 10, range = function(self, t) - if self:getTalentLevel(t) < 3 then return 1 - else return math.floor(self:getTalentLevel(t)) end + return math.ceil(5 + self:getDex(12)) + end, + computeDamage = function(self, t, ammo) + local inc_dam = 0 + local damtype = DamageType.SPELLKNOCKBACK + local particle = "ball_fire" + inc_dam = self:getTalentLevel(self.T_FIRE_INFUSION) * 0.05 + (ammo.alchemist_bomb.power or 0) / 100 + local dam = self:combatTalentSpellDamage(t, 15, 120, (ammo.alchemist_power + self:combatSpellpower()) / 2) + dam = dam * (1 + inc_dam) + return dam, damtype, particle end, action = function(self, t) - local tg = {type="beam", range=self:getTalentRange(t), talent=t} - if self:getTalentLevel(t) >= 3 then tg.type = "beam" end + local ammo = self:hasAlchemistWeapon() + if not ammo or ammo:getNumber() < 2 then + game.logPlayer(self, "You need to ready at least two alchemist gems in your quiver.") + return + end + + local tg = {type="ball", range=self:getTalentRange(t)+(ammo.alchemist_bomb.range or 0), radius=2, talent=t} local x, y = self:getTarget(tg) if not x or not y then return nil end - self:project(tg, x, y, function(tx, ty) + + ammo = self:removeObject(self:getInven("QUIVER"), 1) + if not ammo then return end + + local dam, damtype, particle = t.computeDamage(self, t, ammo) + local prot = self:getTalentLevelRaw(self.T_ALCHEMIST_PROTECTION) * 0.2 + local golem = game.level:hasEntity(self.alchemy_golem) and self.alchemy_golem or nil + local dam_done = 0 + + local tmp = {} + local grids = self:project(tg, x, y, function(tx, ty) + local d = dam + -- Protect yourself + if tx == self.x and ty == self.y then d = dam * (1 - prot) end + -- Protect the golem + if golem and tx == golem.x and ty == golem.y then d = dam * (1 - prot) end + + DamageType:get(damtype).projector(self, tx, ty, damtype, self:spellCrit(d), tmp) local target = game.level.map(tx, ty, Map.ACTOR) if not target then return end - - if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 10) and target:canBe("stone") and target:canBe("instakill") then - target:setEffect(target.EFF_STONED, math.floor((3 + self:getTalentLevel(t)) / 1.5), {}) - game.level.map:particleEmitter(tx, ty, 1, "archery") + if ammo.alchemist_bomb.splash then + DamageType:get(DamageType[ammo.alchemist_bomb.splash.type]).projector(self, tx, ty, DamageType[ammo.alchemist_bomb.splash.type], ammo.alchemist_bomb.splash.dam) + end + if ammo.alchemist_bomb.stun and rng.percent(ammo.alchemist_bomb.stun.chance) and target:checkHit(self:combatSpellpower(), target:combatPhysicalResist(), 0, 95, 5) and target:canBe("stun") then + target:setEffect(target.EFF_STUNNED, ammo.alchemist_bomb.stun.dur, {}) + end + if ammo.alchemist_bomb.daze and rng.percent(ammo.alchemist_bomb.daze.chance) and target:checkHit(self:combatSpellpower(), target:combatPhysicalResist(), 0, 95, 5) and target:canBe("stun") then + target:setEffect(target.EFF_DAZED, ammo.alchemist_bomb.daze.dur, {}) end end) - game:playSoundNear(self, "talents/earth") + + if ammo.alchemist_bomb.leech then self:heal(math.min(self.max_life * ammo.alchemist_bomb.leech / 100, dam_done)) end + + local _ _, x, y = self:canProject(tg, x, y) + game.level.map:particleEmitter(x, y, tg.radius, particle, {radius=tg.radius, grids=grids, tx=x, ty=y}) + + if ammo.alchemist_bomb.mana then self:incMana(ammo.alchemist_bomb.mana) end + + game:playSoundNear(self, "talents/arcane") return true end, info = function(self, t) - return ([[Touch your foe and turn it into stone for %d turns. - Stoned creatures are unable to act or regen life and are very brittle. - If a stoned creature if hit by an attack that deals more than 30%% of its life it will shatter and be destroyed. - Stoned creatures are highly resistant to fire and lightning and somewhat resistant to physical attacks. - At level 3 it will become a beam.]]):format(math.floor((3 + self:getTalentLevel(t)) / 1.5)) + local ammo = self:hasAlchemistWeapon() + local dam, damtype = 1 + if ammo then dam = t.computeDamage(self, t, ammo) end + return ([[Crush together two alchemmist gems, this makes them extremely unstable. + You then throw them to a target area, they explode on impact dealing %0.2f physical damage and knocking back any creatures in the blast radius. + Each kind of gem will also provide a specific effect. + The damage will improve with better gems and Magic stat and the range with your dexterity.]]):format(dam) end, } diff --git a/game/modules/tome/data/talents/spells/fire-alchemy.lua b/game/modules/tome/data/talents/spells/fire-alchemy.lua new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/game/modules/tome/data/talents/spells/gemology.lua b/game/modules/tome/data/talents/spells/stone-alchemy.lua similarity index 76% rename from game/modules/tome/data/talents/spells/gemology.lua rename to game/modules/tome/data/talents/spells/stone-alchemy.lua index 961dd285dc214f755d79a9ffbd8a0767fc3d037e..e06e4dc0ce3690b59f1233c277f23140f4f8d634 100644 --- a/game/modules/tome/data/talents/spells/gemology.lua +++ b/game/modules/tome/data/talents/spells/stone-alchemy.lua @@ -19,7 +19,7 @@ newTalent{ name = "Create Alchemist Gems", - type = {"spell/gemology-base", 1}, + type = {"spell/stone-alchemy-base", 1}, require = spells_req1, points = 1, range = function(self, t) @@ -58,7 +58,7 @@ newTalent{ newTalent{ name = "Extract Gems", - type = {"spell/gemology", 1}, + type = {"spell/stone-alchemy", 1}, require = spells_req1, points = 5, mana = 5, @@ -84,7 +84,7 @@ newTalent{ newTalent{ name = "Imbue Item", - type = {"spell/gemology", 2}, + type = {"spell/stone-alchemy", 2}, require = spells_req2, points = 5, mana = 80, @@ -106,39 +106,10 @@ newTalent{ You can only imbue items once, and it is permanent.]]) end, } - -newTalent{ - name = "Gem Vision", - type = {"spell/gemology", 3}, - require = spells_req3, - cooldown = 10, - mana = 15, - points = 5, - action = function(self, t) - local ammo = self:hasAlchemistWeapon() - if not ammo or ammo:getNumber() < 2 then - game.logPlayer(self, "You need to ready 2 alchemist gems in your quiver.") - return - end - - local x, y = self:getTarget{type="ball", nolock=true, no_restrict=true, range=100, radius=3 + self:getTalentLevel(t)} - if not x then return nil end - - for i = 1, 2 do self:removeObject(self:getInven("QUIVER"), 1) end - self:magicMap(3 + self:getTalentLevel(t), x, y) - game:playSoundNear(self, "talents/spell_generic2") - return true - end, - info = function(self, t) - return ([[Consumes two alchemist gems to see remote areas in a radius of %d.]]): - format(3 + self:getTalentLevel(t)) - end, -} - newTalent{ name = "Gem Portal", - type = {"spell/gemology",4}, - require = spells_req4, + type = {"spell/stone-alchemy",3}, + require = spells_req3, cooldown = 20, mana = 20, points = 5, @@ -167,3 +138,40 @@ newTalent{ format(math.floor(4 + self:combatSpellpower(0.06) * self:getTalentLevel(t))) end, } + +newTalent{ + name = "Stone Touch", + type = {"spell/stone-alchemy",4}, + require = spells_req4, + points = 5, + mana = 80, + cooldown = 15, + range = function(self, t) + if self:getTalentLevel(t) < 3 then return 1 + else return math.floor(self:getTalentLevel(t)) end + end, + action = function(self, t) + local tg = {type="beam", range=self:getTalentRange(t), talent=t} + if self:getTalentLevel(t) >= 3 then tg.type = "beam" end + local x, y = self:getTarget(tg) + if not x or not y then return nil end + self:project(tg, x, y, function(tx, ty) + local target = game.level.map(tx, ty, Map.ACTOR) + if not target then return end + + if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 10) and target:canBe("stone") and target:canBe("instakill") then + target:setEffect(target.EFF_STONED, math.floor((3 + self:getTalentLevel(t)) / 1.5), {}) + game.level.map:particleEmitter(tx, ty, 1, "archery") + end + end) + game:playSoundNear(self, "talents/earth") + return true + end, + info = function(self, t) + return ([[Touch your foe and turn it into stone for %d turns. + Stoned creatures are unable to act or regen life and are very brittle. + If a stoned creature is hit by an attack that deals more than 30%% of its life it will shatter and be destroyed. + Stoned creatures are highly resistant to fire and lightning and somewhat resistant to physical attacks. + At level 3 it will become a beam.]]):format(math.floor((3 + self:getTalentLevel(t)) / 1.5)) + end, +} diff --git a/ideas/classes.ods b/ideas/classes.ods index 904bd394f3d737e6f3a95cc012ccfa7a1d81ef33..1a8943c80a4bd32d39622ad07a5b0e70e020a294 100644 Binary files a/ideas/classes.ods and b/ideas/classes.ods differ diff --git a/ideas/crafting.ods b/ideas/crafting.ods index 18377387632cbb4fb1de8ad513e13fb0c4d2ed38..c8e23c871496918e33dbaefce3d6573b495de7d6 100644 Binary files a/ideas/crafting.ods and b/ideas/crafting.ods differ