From 6f074c92394bf7642577476b3821cd016cb48caa Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 4 Apr 2026 17:18:33 +0200 Subject: [PATCH 01/28] Implement summon effects in OpenMW --- Data Files/Tamriel_Data.omwscripts | 4 + Data Files/l10n/TamrielData/en.yaml | 59 ++++++- .../scripts/TamrielData/actor_magic.lua | 147 ++++++++++++++++++ .../scripts/TamrielData/actor_summons.lua | 111 +++++++++++++ .../scripts/TamrielData/global_summons.lua | 27 ++++ Data Files/scripts/TamrielData/load_magic.lua | 101 ++++++++++++ 6 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 Data Files/scripts/TamrielData/actor_magic.lua create mode 100644 Data Files/scripts/TamrielData/actor_summons.lua create mode 100644 Data Files/scripts/TamrielData/global_summons.lua create mode 100644 Data Files/scripts/TamrielData/load_magic.lua diff --git a/Data Files/Tamriel_Data.omwscripts b/Data Files/Tamriel_Data.omwscripts index 3c47086..00a6b50 100644 --- a/Data Files/Tamriel_Data.omwscripts +++ b/Data Files/Tamriel_Data.omwscripts @@ -5,3 +5,7 @@ GLOBAL: scripts/TamrielData/global_mwscript_variable.lua PLAYER: scripts/TamrielData/player_magic.lua PLAYER: scripts/TamrielData/player_restrict_equipment.lua MENU: scripts/TamrielData/menu_version_warning.lua +PLAYER, NPC, CREATURE: scripts/TamrielData/actor_magic.lua +PLAYER, NPC, CREATURE: scripts/TamrielData/actor_summons.lua +GLOBAL: scripts/TamrielData/global_summons.lua +LOAD: scripts/TamrielData/load_magic.lua diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 13047df..75040a6 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -21,4 +21,61 @@ TamrielData_magic_passwallWard: "You cannot pass through to there." TamrielData_magic_passwallAlpha: "You cannot pass through that." TamrielData_magic_passwallExterior: "You must be in a confined space." TamrielData_magic_passwallDoorExterior: "You cannot leave a confined space." -TamrielData_magic_passwallUnderwater: "You cannot be underwater." \ No newline at end of file +TamrielData_magic_passwallUnderwater: "You cannot be underwater." + +Magic_T_summon_Devourer: "Summon Devourer" +Magic_T_summon_DevourerDesc: "This effect summons a devourer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_DremArch: "Summon Dremora Archer" +Magic_T_summon_DremArchDesc: "This effect summons a dremora archer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_DremCast: "Summon Dremora Spellcaster" +Magic_T_summon_DremCastDesc: "This effect summons a dremora spellcaster from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Guardian: "Summon Guardian" +Magic_T_summon_GuardianDesc: "This effect summons a guardian from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_LesserClfr: "Summon Rock Biter Clannfear" +Magic_T_summon_LesserClfrDesc: "This effect summons a rock biter clannfear from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Ogrim: "Summon Ogrim" +Magic_T_summon_OgrimDesc: "This effect summons an ogrim from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Seducer: "Summon Seducer" +Magic_T_summon_SeducerDesc: "This effect summons a seducer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_SeducerDark: "Summon Dark Seducer" +Magic_T_summon_SeducerDarkDesc: "This effect summons a dark seducer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Vermai: "Summon Vermai" +Magic_T_summon_VermaiDesc: "This effect summons a vermai from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_AtroStormMon: "Summon Storm Monarch" +Magic_T_summon_AtroStormMonDesc: "This effect summons a storm monarch from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_IceWraith: "Summon Ice Wraith" +Magic_T_summon_IceWraithDesc: "This effect summons an ice wraith from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_DweSpectre: "Summon Dwarven Spectre" +Magic_T_summon_DweSpectreDesc: "This effect summons a dwarven spectre from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_SteamCent: "Summon Steam Centurion" +Magic_T_summon_SteamCentDesc: "This effect summons an steam centurion from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_SpiderCent: "Summon Centurion Spider" +Magic_T_summon_SpiderCentDesc: "This effect summons a centurion spider from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_WelkyndSpirit: "Summon Welkynd Spirit" +Magic_T_summon_WelkyndSpiritDesc: "This effect summons a welkynd spirit from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Auroran: "Summon Auroran" +Magic_T_summon_AuroranDesc: "This effect summons an auroran from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Herne: "Summon Herne" +Magic_T_summon_HerneDesc: "This effect summons a herne from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Morphoid: "Summon Morphoid Daedra" +Magic_T_summon_MorphoidDesc: "This effect summons a morphoid daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Draugr: "Summon Draugr" +Magic_T_summon_DraugrDesc: "This effect summons a draugr from the Underworld. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Underworld. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Spriggan: "Summon Spriggan" +Magic_T_summon_SprigganDesc: "This effect summons a spriggan from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_BoneldGr: "Summon Bonelord Warder" +Magic_T_summon_BoneldGrDesc: "This effect summons a bonelord warder from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Ghost: "Summon Ghost" +Magic_T_summon_GhostDesc: "This effect summons a ghost from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Wraith: "Summon Wraith" +Magic_T_summon_WraithDesc: "This effect summons a wraith from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_Barrowguard: "Summon Barrowguard" +Magic_T_summon_BarrowguardDesc: "This effect summons a barrowguard from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_MinoBarrowguard: "Summon Minotaur Barrowguard" +Magic_T_summon_MinoBarrowguardDesc: "This effect summons a minotaur barrowguard from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_SkeletonChampion: "Summon Skeleton Champion" +Magic_T_summon_SkeletonChampionDesc: "This effect summons a skeleton champion from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_AtroFrostMon: "Summon Frost Monarch" +Magic_T_summon_AtroFrostMonDesc: "This effect summons a frost monarch from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_T_summon_SpiderDaedra: "Summon Spider Daedra" +Magic_T_summon_SpiderDaedraDesc: "This effect summons a spider daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua new file mode 100644 index 0000000..9b57807 --- /dev/null +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -0,0 +1,147 @@ +local core = require('openmw.core') + +if core.API_REVISION < 125 then + return +end + +local types = require('openmw.types') +local self = require('openmw.self') +local auxUtil = require('openmw_aux.util') + +local effectStartHandlers = {} +local effectUpdateHandlers = {} +local effectEndHandlers = {} + +local function onEffectStart(spell, effect) + auxUtil.callEventHandlers(effectStartHandlers, spell, effect) +end + +local function onEffectUpdate(spell, effect) + auxUtil.callEventHandlers(effectUpdateHandlers, spell, effect) +end + +local function onEffectEnd(id, index) + auxUtil.callEventHandlers(effectEndHandlers, id, index) +end + +local STATE_INIT = 0 +local STATE_ACTIVE = 1 +local STATE_ONCE = 2 + +local appliedOnce = {} +for _, effect in pairs(core.magic.effects.records) do + if effect.isAppliedOnce then + appliedOnce[effect.id] = true + end +end + +local state + +local activeSpells = types.Actor.activeSpells(self) + +-- This should all be replaced with built in OpenMW stuff, but that doesn't exist yet. This code cannot track effect lifecycles properly +local function updateEffects() + local active = {} + for _, spell in pairs(activeSpells) do + local id = spell.activeSpellId + active[id] = true + local effects = state[id] + if effects == nil then + effects = {} + state[id] = effects + for _, effect in pairs(spell.effects) do + effects[effect.index] = STATE_INIT + onEffectStart(spell, effect) + end + else + local activeIndices = {} + for _, effect in pairs(spell.effects) do + local index = effect.index + activeIndices[index] = true + local s = effects[index] + if s == nil then + effects[index] = STATE_INIT + onEffectStart(spell, effect) + elseif s == STATE_INIT then + if appliedOnce[effect.id] then + effects[index] = STATE_ONCE + else + s = STATE_ACTIVE + effects[index] = s + end + end + if s == STATE_ACTIVE then + onEffectUpdate(spell, effect) + end + end + for index, _ in pairs(effects) do + if activeIndices[index] == nil then + onEffectEnd(id, index) + effects[index] = nil + end + end + end + end + for id, effects in pairs(state) do + if active[id] == nil then + for index, _ in pairs(effects) do + onEffectEnd(id, index) + end + state[id] = nil + end + end +end + +local function onInit() + state = {} + for _, spell in pairs(activeSpells) do + local effects = {} + for _, effect in pairs(spell.effects) do + effects[effect.index] = STATE_INIT + end + state[spell.activeSpellId] = effects + end +end + +return { + engineHandlers = { + onInit = onInit, + onSave = function() + return state + end, + onLoad = function(data) + if data == nil then + onInit() + else + state = data + end + end, + onUpdate = updateEffects + }, + interfaceName = 'T_ActorMagic', + interface = { + version = 1, + addEffectStartHandler = function(handler) + effectStartHandlers[#effectStartHandlers + 1] = handler + end, + addEffectUpdateHandler = function(handler) + effectUpdateHandlers[#effectUpdateHandlers + 1] = handler + end, + addEffectEndHandler = function(handler) + effectEndHandlers[#effectEndHandlers + 1] = handler + end, + removeEffect = function(id, index) + -- This isn't possible and this implementation is slightly wrong, but it's better than nothing + local effects = state[id] + if effects == nil then + return + end + for i, _ in pairs(effects) do + if i ~= index then + return + end + end + activeSpells:remove(id) + end + } +} diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua new file mode 100644 index 0000000..0426b53 --- /dev/null +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -0,0 +1,111 @@ +local core = require('openmw.core') + +if core.API_REVISION < 125 then + return +end + +local I = require('openmw.interfaces') +local self = require('openmw.self') + +local summons = { + t_summon_devourer = 't_dae_cre_devourer_01', + t_summon_dremarch = 't_dae_cre_drem_arch_01', + t_summon_dremcast = 't_dae_cre_drem_cast_01', + t_summon_guardian = 't_dae_cre_guardian_01', + t_summon_lesserclfr = 't_dae_cre_lesserclfr_01', + t_summon_ogrim = 'ogrim', + t_summon_seducer = 't_dae_cre_seduc_01', + t_summon_seducerdark = 't_dae_cre_seducdark_02', + t_summon_vermai = 't_dae_cre_verm_01', + t_summon_atrostormmon = 't_dae_cre_monarchst_01', + t_summon_icewraith = 't_sky_cre_icewr_01', + t_summon_dwespectre = 'dwarven ghost', + t_summon_steamcent = 'centurion_steam', + t_summon_spidercent = 'centurion_spider', + t_summon_welkyndspirit = 't_ayl_cre_welkspr_01', + t_summon_auroran = 't_dae_cre_auroran_01', + t_summon_herne = 't_dae_cre_herne_01', + t_summon_morphoid = 't_dae_cre_morphoid_01', + t_summon_draugr = 't_sky_und_drgr_01', + t_summon_spriggan = 't_sky_cre_spriggan_01', + t_summon_boneldgr = 't_mw_und_boneldgr_01', + t_summon_ghost = 't_cyr_und_ghst_01', + t_summon_wraith = 't_cyr_und_wrth_01', + t_summon_barrowguard = 't_cyr_und_mum_01', + t_summon_minobarrowguard = 't_cyr_und_minobarrow_01', + t_summon_skeletonchampion = 't_glb_und_skelcmpgls_01', + t_summon_atrofrostmon = 't_dae_cre_monarchfr_01', + t_summon_spiderdaedra = 't_dae_cre_spiderdae_01', +} + +local state = { + summons = {} +} + +local function toKey(id, index) + return id .. ',' .. index +end + +I.T_ActorMagic.addEffectStartHandler(function(spell, effect) + local creature = summons[effect.id] + if creature == nil then + return + end + local id = spell.activeSpellId + local index = effect.index + local key = toKey(id, index) + state.summons[key] = { id = id, index = index } + core.sendGlobalEvent('T_Summon', { key = key, creature = creature, caster = self.object }) +end) + +I.T_ActorMagic.addEffectEndHandler(function(id, index) + local key = toKey(id, index) + local summon = state.summons[key] + if summon == nil then + return + end + local creature = summon.creature + if creature and creature:isValid() then + core.sendGlobalEvent('T_Unsummon', { creature = creature }) + end + state.summons[key] = nil +end) + +return { + eventHandlers = { + T_Summoned = function(data) + local summon = state.summons[data.key] + if summon == nil then + summon = {} + state.summons[data.key] = summon + end + summon.creature = data.creature + end, + T_SummonDied = function(data) + local summon = state.summons[data.key] + if summon ~= nil and summon.id ~= nil and summon.index ~= nil then + I.T_ActorMagic.removeEffect(summon.id, summon.index) + end + end, + T_MarkSummon = function(data) + state.caster = data.caster + state.key = data.key + end, + Died = function() + if state.key ~= nil then + core.sendGlobalEvent('T_Unsummon', { creature = self.object }) + if state.caster:isValid() then + state.caster:sendEvent('T_SummonDied', { key = state.key }) + end + end + end + }, + engineHandlers = { + onSave = function() + return state + end, + onLoad = function(data) + state = data + end + } +} diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua new file mode 100644 index 0000000..64a8ee5 --- /dev/null +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -0,0 +1,27 @@ +local types = require('openmw.types') +local util = require('openmw.util') +local world = require('openmw.world') + +local distance = util.vector3(0, 120, 0) + +local startVfx = types.Static.records['VFX_Summon_Start'].model +local endVfx = types.Static.records['VFX_Summon_End'].model + +return { + eventHandlers = { + T_Summon = function(data) + local creature = world.createObject(data.creature) + local caster = data.caster + local position = caster.rotation:apply(distance) + caster.position + creature:teleport(caster.cell.name, position, { onGround = true, rotation = caster.rotation }) + creature:sendEvent('StartAIPackage', { type = 'Follow', target = caster }) + creature:sendEvent('T_MarkSummon', { key = data.key, caster = caster }) + caster:sendEvent('T_Summoned', { key = data.key, creature = creature }) + world.vfx.spawn(startVfx, position) + end, + T_Unsummon = function(data) + data.creature.enabled = false + world.vfx.spawn(startVfx, data.creature.position) + end + } +} diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua new file mode 100644 index 0000000..ed88288 --- /dev/null +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -0,0 +1,101 @@ +local content = require('openmw.content') +local core = require('openmw.core') + +local summonTemplates = { + automaton = 'summoncenturionsphere', + creature = 'summonbear', + daedra = 'summonhunger', + undead = 'summonancestralghost' +} + +local summonEffects = { + T_summon_Devourer = { 52, 'td\\s\\td_s_summ_dev.dds', 'daedra' }, + T_summon_DremArch = { 33, 'td\\s\\td_s_sum_drm_arch.dds', 'daedra' }, + T_summon_DremCast = { 31, 'td\\s\\td_s_sum_drm_mage.dds', 'daedra' }, + T_summon_Guardian = { 69, 'td\\s\\td_s_sum_guard.dds', 'daedra' }, + T_summon_LesserClfr = { 19, 'td\\s\\td_s_sum_lsr_clan.dds', 'daedra' }, + T_summon_Ogrim = { 33, 'td\\s\\td_s_summ_ogrim.dds', 'daedra' }, + T_summon_Seducer = { 52, 'td\\s\\td_s_summ_sed.dds', 'daedra' }, + T_summon_SeducerDark = { 75, 'td\\s\\td_s_summ_d_sed.dds', 'daedra' }, + T_summon_Vermai = { 29, 'td\\s\\td_s_summ_vermai.dds', 'daedra' }, + T_summon_AtroStormMon = { 60, 'td\\s\\td_s_sum_stm_monch.dds', 'daedra' }, + T_summon_IceWraith = { 35, 'td\\s\\td_s_sum_ice_wrth.dds', 'undead' }, + T_summon_DweSpectre = { 17, 'td\\s\\td_s_sum_dwe_spctre.dds', 'undead' }, + T_summon_SteamCent = { 29, 'td\\s\\td_s_sum_dwe_cent.dds', 'automaton' }, + T_summon_SpiderCent = { 15, 'td\\s\\td_s_sum_dwe_spdr.dds', 'automaton' }, + T_summon_WelkyndSpirit = { 29, 'td\\s\\td_s_sum_welk_srt.dds', 'undead' }, + T_summon_Auroran = { 46, 'td\\s\\td_s_sum_auro.dds', 'daedra' }, + T_summon_Herne = { 18, 'td\\s\\td_s_sum_herne.dds', 'daedra' }, + T_summon_Morphoid = { 21, 'td\\s\\td_s_sum_morph.dds', 'daedra' }, + T_summon_Draugr = { 29, 'td\\s\\td_s_sum_draugr.dds', 'undead' }, + T_summon_Spriggan = { 48, 'td\\s\\td_s_sum_sprig.dds', 'creature' }, + T_summon_BoneldGr = { 71, 'td\\s\\td_s_sum_gtr_bnlrd.dds', 'undead' }, + T_summon_Ghost = { 7, 'td\\s\\td_s_summ_ghost.dds', 'undead' }, + T_summon_Wraith = { 49, 'td\\s\\td_s_summ_wraith.dds', 'undead' }, + T_summon_Barrowguard = { 11, 'td\\s\\td_s_summ_brwgurd.dds', 'undead' }, + T_summon_MinoBarrowguard = { 57, 'td\\s\\td_s_summ_mintur.dds', 'undead' }, + T_summon_SkeletonChampion = { 32, 'td\\s\\td_s_sum_skele_c.dds', 'undead' }, + T_summon_AtroFrostMon = { 47, 'td\\s\\td_s_sum_fst_monch.dds', 'daedra' }, + T_summon_SpiderDaedra = { 42, 'td\\s\\td_s_sum_spidr_dae.dds', 'daedra' }, +} + +local summonSpells = { + T_Com_Cnj_SummonDevourer = { 156, 'T_summon_Devourer', 60 }, + T_Com_Cnj_SummonDremoraArcher = { 98, 'T_summon_DremArch', 60 }, + T_Com_Cnj_SummonDremoraCaster = { 93, 'T_summon_DremCast', 60 }, + T_Com_Cnj_SummonGuardian = { 155, 'T_summon_Guardian', 45 }, + T_Com_Cnj_SummonLesserClannfear = { 57, 'T_summon_LesserClfr', 60 }, + T_Com_Cnj_SummonOgrim = { 99, 'T_summon_Ogrim', 60 }, + T_Com_Cnj_SummonSeducer = { 156, 'T_summon_Seducer', 60 }, + T_Com_Cnj_SummonSeducerDark = { 169, 'T_summon_SeducerDark', 45 }, + T_Com_Cnj_SummonVermai = { 88, 'T_summon_Vermai', 60 }, + T_Com_Cnj_SummonStormMonarch = { 180, 'T_summon_AtroStormMon', 60 }, + T_Nor_Cnj_SummonIceWraith = { 105, 'T_summon_IceWraith', 60 }, + T_Dwe_Cnj_Uni_SummonDweSpectre = { 52, 'T_summon_DweSpectre', 60 }, + T_Dwe_Cnj_Uni_SummonSteamCent = { 88, 'T_summon_SteamCent', 60 }, + T_Dwe_Cnj_Uni_SummonSpiderCent = { 45, 'T_summon_SpiderCent', 60 }, + T_Ayl_Cnj_SummonWelkyndSpirit = { 78, 'T_summon_WelkyndSpirit', 60 }, + T_Com_Cnj_SummonAuroran = { 138, 'T_summon_Auroran', 60 }, + T_Com_Cnj_SummonHerne = { 54, 'T_summon_Herne', 60 }, + T_Com_Cnj_SummonMorphoid = { 63, 'T_summon_Morphoid', 60 }, + T_Nor_Cnj_SummonDraugr = { 78, 'T_summon_Draugr', 60 }, + T_Nor_Cnj_SummonSpriggan = { 144, 'T_summon_Spriggan', 60 }, + T_De_Cnj_SummonGreaterBonelord = { 160, 'T_summon_BoneldGr', 45 }, + T_Cr_Cnj_AylSorcKSummon1 = { 40, 'T_summon_Auroran', 40 }, + T_Cr_Cnj_AylSorcKSummon3 = { 25, 'T_summon_WelkyndSpirit', 40 }, + T_Cyr_Cnj_SummonWraith = { 147, 'T_summon_Wraith', 60 }, + T_Cyr_Cnj_SummonBarrowguard = { 33, 'T_summon_Barrowguard', 60 }, + T_Cyr_Cnj_SummonMinoBarrowguard = { 171, 'T_summon_MinoBarrowguard', 60 }, + T_Com_Cnj_SummonSkeletonChamp = { 96, 'T_summon_SkeletonChampion', 60 }, + T_Com_Cnj_SummonFrostMonarch = { 141, 'T_summon_AtroFrostMon', 60 }, + T_Com_Cnj_SummonSpiderDaedra = { 126, 'T_summon_SpiderDaedra', 60 }, +} + +local l10n = core.l10n('TamrielData') + +local function addSummons() + local effects = content.magicEffects.records + for id, values in pairs(summonEffects) do + local cost = values[1] + local icon = values[2] + local template = values[3] + effects[id] = { template = effects[summonTemplates[template]], cost = cost, icon = icon, name = l10n('Magic_' .. id), description = l10n('Magic_' .. id .. 'Desc'), allowsSpellmaking = true, allowsEnchanting = true } + end + local spells = content.spells.records + local type = content.spells.TYPE.Spell + local range = content.RANGE.Self + for id, values in pairs(summonSpells) do + local cost = values[1] + local effect = values[2] + local duration = values[3] + spells[id] = { cost = cost, type = type, isAutocalc = false, starterSpellFlag = false, name = l10n('Magic_' .. effect), effects = { { duration = duration, id = effect, range = range } } } + end +end + +return { + engineHandlers = { + onContentFilesLoaded = function() + addSummons() + end + } +} From 67c1c52aae569fe46739d8ccb7178e4dde844438 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 5 Apr 2026 11:32:57 +0200 Subject: [PATCH 02/28] Fix positioning, vfx, hostility, and update effects upon going inactive --- Data Files/scripts/TamrielData/actor_magic.lua | 3 ++- Data Files/scripts/TamrielData/actor_summons.lua | 1 + Data Files/scripts/TamrielData/global_summons.lua | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 9b57807..900b122 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -116,7 +116,8 @@ return { state = data end end, - onUpdate = updateEffects + onUpdate = updateEffects, + onInactive = updateEffects }, interfaceName = 'T_ActorMagic', interface = { diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index 0426b53..2d8a4a8 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -90,6 +90,7 @@ return { T_MarkSummon = function(data) state.caster = data.caster state.key = data.key + self.type.stats.ai.fight(self).base = 30 -- we should probably be using dedicated creature variants end, Died = function() if state.key ~= nil then diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index 64a8ee5..1837249 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -2,7 +2,7 @@ local types = require('openmw.types') local util = require('openmw.util') local world = require('openmw.world') -local distance = util.vector3(0, 120, 0) +local distance = util.vector3(0, 120, 30) local startVfx = types.Static.records['VFX_Summon_Start'].model local endVfx = types.Static.records['VFX_Summon_End'].model @@ -12,12 +12,13 @@ return { T_Summon = function(data) local creature = world.createObject(data.creature) local caster = data.caster - local position = caster.rotation:apply(distance) + caster.position - creature:teleport(caster.cell.name, position, { onGround = true, rotation = caster.rotation }) + local rotation = util.transform.rotateZ(caster.rotation:getYaw()) + local position = rotation:apply(distance) + caster.position + creature:teleport(caster.cell.name, position, { onGround = true, rotation = rotation }) creature:sendEvent('StartAIPackage', { type = 'Follow', target = caster }) creature:sendEvent('T_MarkSummon', { key = data.key, caster = caster }) + creature:sendEvent('AddVfx', { model = startVfx }) caster:sendEvent('T_Summoned', { key = data.key, creature = creature }) - world.vfx.spawn(startVfx, position) end, T_Unsummon = function(data) data.creature.enabled = false From b37d9192b0df4c9c4200fa428a0bd7d7e07113fb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 8 Apr 2026 17:12:09 +0200 Subject: [PATCH 03/28] Avoid spawning summons behind walls --- .../scripts/TamrielData/actor_summons.lua | 32 ++++++++++++++++++- .../scripts/TamrielData/global_summons.lua | 7 +--- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index 2d8a4a8..a049e3b 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -5,7 +5,9 @@ if core.API_REVISION < 125 then end local I = require('openmw.interfaces') +local nearby = require('openmw.nearby') local self = require('openmw.self') +local util = require('openmw.util') local summons = { t_summon_devourer = 't_dae_cre_devourer_01', @@ -46,6 +48,34 @@ local function toKey(id, index) return id .. ',' .. index end +local FRONT = 0 +local BACK = 3 +local LEFT = 2 +local RIGHT = 1 +local collisionType = nearby.COLLISION_TYPE.World + nearby.COLLISION_TYPE.Door + +local function getSafeSpawn() + local origin = self.position + util.vector3(0, 0, 20) + local rotation = util.transform.rotateZ(self.rotation:getYaw()) + for direction = FRONT,BACK do + local spawn + if direction == FRONT then + spawn = origin + rotation:apply(util.vector3(0, 120, 10)) + elseif direction == BACK then + spawn = origin - rotation:apply(util.vector3(0, 120, 10)) + elseif direction == LEFT then + spawn = origin - rotation:apply(util.vector3(120, 0, 10)) + elseif direction == RIGHT then + spawn = origin + rotation:apply(util.vector3(120, 0, 10)) + end + local result = nearby.castRay(spawn, origin, { collisionType = collisionType }) + if not result.hit then + return spawn + end + end + return origin +end + I.T_ActorMagic.addEffectStartHandler(function(spell, effect) local creature = summons[effect.id] if creature == nil then @@ -55,7 +85,7 @@ I.T_ActorMagic.addEffectStartHandler(function(spell, effect) local index = effect.index local key = toKey(id, index) state.summons[key] = { id = id, index = index } - core.sendGlobalEvent('T_Summon', { key = key, creature = creature, caster = self.object }) + core.sendGlobalEvent('T_Summon', { key = key, creature = creature, caster = self.object, position = getSafeSpawn() }) end) I.T_ActorMagic.addEffectEndHandler(function(id, index) diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index 1837249..12c2a3c 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -1,9 +1,6 @@ local types = require('openmw.types') -local util = require('openmw.util') local world = require('openmw.world') -local distance = util.vector3(0, 120, 30) - local startVfx = types.Static.records['VFX_Summon_Start'].model local endVfx = types.Static.records['VFX_Summon_End'].model @@ -12,9 +9,7 @@ return { T_Summon = function(data) local creature = world.createObject(data.creature) local caster = data.caster - local rotation = util.transform.rotateZ(caster.rotation:getYaw()) - local position = rotation:apply(distance) + caster.position - creature:teleport(caster.cell.name, position, { onGround = true, rotation = rotation }) + creature:teleport(caster.cell.name, data.position, { onGround = true }) creature:sendEvent('StartAIPackage', { type = 'Follow', target = caster }) creature:sendEvent('T_MarkSummon', { key = data.key, caster = caster }) creature:sendEvent('AddVfx', { model = startVfx }) From 65171366407af1236e3517d0445c198d5960d01b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 9 Apr 2026 21:23:54 +0200 Subject: [PATCH 04/28] Use MWSE data --- .../MWSE/mods/TamrielData/magicdata.lua | 59 ++++---- Data Files/l10n/TamrielData/en.yaml | 112 +++++++------- Data Files/l10n/TamrielData/fr.yaml | 59 +++++++- Data Files/l10n/TamrielData/pl.yaml | 59 +++++++- .../scripts/TamrielData/actor_summons.lua | 35 +---- Data Files/scripts/TamrielData/load_magic.lua | 138 +++++++----------- 6 files changed, 263 insertions(+), 199 deletions(-) diff --git a/Data Files/MWSE/mods/TamrielData/magicdata.lua b/Data Files/MWSE/mods/TamrielData/magicdata.lua index 618bf18..393c717 100644 --- a/Data Files/MWSE/mods/TamrielData/magicdata.lua +++ b/Data Files/MWSE/mods/TamrielData/magicdata.lua @@ -1,35 +1,38 @@ +-- NB: This file is shared between the MWSE and OpenMW implementations + + -- The effect costs for most summons were initially calculated by mort using a formula (dependent on a creature's health and soul) that is now lost and were then adjusted as seemed reasonable. -- Calculations have provided a new formula: Effect Cost = (.16 * Health) + (.035 * Soul); most of the old values are in close agreement with the new formula and have thus been left unchanged. -- effect id, effect name, creature id, effect mana cost, icon, effect description local td_summon_effects = { - { "T_summon_Devourer", "summonDevourer", "T_Dae_Cre_Devourer_01", 52, "td\\s\\td_s_summ_dev.dds", "summonDevourerDesc" }, - { "T_summon_DremArch", "summonDremoraArcher", "T_Dae_Cre_Drem_Arch_01", 33, "td\\s\\td_s_sum_drm_arch.dds", "summonDremoraArcherDesc" }, - { "T_summon_DremCast", "summonDremoraCaster", "T_Dae_Cre_Drem_Cast_01", 31, "td\\s\\td_s_sum_drm_mage.dds", "summonDremoraCasterDesc" }, - { "T_summon_Guardian", "summonGuardian", "T_Dae_Cre_Guardian_01", 69, "td\\s\\td_s_sum_guard.dds", "summonGuardianDesc" }, - { "T_summon_LesserClfr", "summonLesserClannfear", "T_Dae_Cre_LesserClfr_01", 19, "td\\s\\td_s_sum_lsr_clan.dds", "summonLesserClannfearDesc" }, - { "T_summon_Ogrim", "summonOgrim", "ogrim", 33, "td\\s\\td_s_summ_ogrim.dds", "summonOgrimDesc" }, - { "T_summon_Seducer", "summonSeducer", "T_Dae_Cre_Seduc_01", 52, "td\\s\\td_s_summ_sed.dds", "summonSeducerDesc" }, - { "T_summon_SeducerDark", "summonSeducerDark", "T_Dae_Cre_SeducDark_02", 75, "td\\s\\td_s_summ_d_sed.dds", "summonSeducerDarkDesc" }, - { "T_summon_Vermai", "summonVermai", "T_Dae_Cre_Verm_01", 29, "td\\s\\td_s_summ_vermai.dds", "summonVermaiDesc" }, - { "T_summon_AtroStormMon", "summonStormMonarch", "T_Dae_Cre_MonarchSt_01", 60, "td\\s\\td_s_sum_stm_monch.dds", "summonStormMonarchDesc" }, - { "T_summon_IceWraith", "summonIceWraith", "T_Sky_Cre_IceWr_01", 35, "td\\s\\td_s_sum_ice_wrth.dds", "summonIceWraithDesc" }, - { "T_summon_DweSpectre", "summonDweSpectre", "dwarven ghost", 17, "td\\s\\td_s_sum_dwe_spctre.dds", "summonDweSpectreDesc" }, - { "T_summon_SteamCent", "summonSteamCent", "centurion_steam", 29, "td\\s\\td_s_sum_dwe_cent.dds", "summonSteamCentDesc" }, - { "T_summon_SpiderCent", "summonSpiderCent", "centurion_spider", 15, "td\\s\\td_s_sum_dwe_spdr.dds", "summonSpiderCentDesc" }, - { "T_summon_WelkyndSpirit", "summonWelkyndSpirit", "T_Ayl_Cre_WelkSpr_01", 29, "td\\s\\td_s_sum_welk_srt.dds", "summonWelkyndSpiritDesc" }, - { "T_summon_Auroran", "summonAuroran", "T_Dae_Cre_Auroran_01", 46, "td\\s\\td_s_sum_auro.dds", "summonAuroranDesc" }, - { "T_summon_Herne", "summonHerne", "T_Dae_Cre_Herne_01", 18, "td\\s\\td_s_sum_herne.dds", "summonHerneDesc" }, - { "T_summon_Morphoid", "summonMorphoid", "T_Dae_Cre_Morphoid_01", 21, "td\\s\\td_s_sum_morph.dds", "summonMorphoidDesc" }, - { "T_summon_Draugr", "summonDraugr", "T_Sky_Und_Drgr_01", 29, "td\\s\\td_s_sum_draugr.dds", "summonDraugrDesc" }, - { "T_summon_Spriggan", "summonSpriggan", "T_Sky_Cre_Spriggan_01", 48, "td\\s\\td_s_sum_sprig.dds", "summonSprigganDesc" }, - { "T_summon_BoneldGr", "summonGreaterBonelord", "T_Mw_Und_BoneldGr_01", 71, "td\\s\\td_s_sum_gtr_bnlrd.dds", "summonGreaterBonelordDesc" }, - { "T_summon_Ghost", "summonGhost", "T_Cyr_Und_Ghst_01", 7, "td\\s\\td_s_summ_ghost.dds", "summonGhostDesc" }, - { "T_summon_Wraith", "summonWraith", "T_Cyr_Und_Wrth_01", 49, "td\\s\\td_s_summ_wraith.dds", "summonWraithDesc" }, - { "T_summon_Barrowguard", "summonBarrowguard", "T_Cyr_Und_Mum_01", 11, "td\\s\\td_s_summ_brwgurd.dds", "summonBarrowguardDesc" }, - { "T_summon_MinoBarrowguard", "summonMinoBarrowguard", "T_Cyr_Und_MinoBarrow_01", 57, "td\\s\\td_s_summ_mintur.dds", "summonMinoBarrowguardDesc" }, - { "T_summon_SkeletonChampion", "summonSkeletonChampion", "T_Glb_Und_SkelCmpGls_01", 32, "td\\s\\td_s_sum_skele_c.dds", "summonSkeletonChampionDesc" }, - { "T_summon_AtroFrostMon", "summonFrostMonarch", "T_Dae_Cre_MonarchFr_01", 47, "td\\s\\td_s_sum_fst_monch.dds", "summonFrostMonarchDesc" }, - { "T_summon_SpiderDaedra", "summonSpiderDaedra", "T_Dae_Cre_SpiderDae_01", 42, "td\\s\\td_s_sum_spidr_dae.dds", "summonSpiderDaedraDesc" }, + { "T_summon_Devourer", "summonDevourer", "T_Dae_Cre_Devourer_01", 52, "td\\s\\td_s_summ_dev.dds", "summonDevourerDesc", "summonHunger" }, + { "T_summon_DremArch", "summonDremoraArcher", "T_Dae_Cre_Drem_Arch_01", 33, "td\\s\\td_s_sum_drm_arch.dds", "summonDremoraArcherDesc", "summonHunger" }, + { "T_summon_DremCast", "summonDremoraCaster", "T_Dae_Cre_Drem_Cast_01", 31, "td\\s\\td_s_sum_drm_mage.dds", "summonDremoraCasterDesc", "summonHunger" }, + { "T_summon_Guardian", "summonGuardian", "T_Dae_Cre_Guardian_01", 69, "td\\s\\td_s_sum_guard.dds", "summonGuardianDesc", "summonHunger" }, + { "T_summon_LesserClfr", "summonLesserClannfear", "T_Dae_Cre_LesserClfr_01", 19, "td\\s\\td_s_sum_lsr_clan.dds", "summonLesserClannfearDesc", "summonHunger" }, + { "T_summon_Ogrim", "summonOgrim", "ogrim", 33, "td\\s\\td_s_summ_ogrim.dds", "summonOgrimDesc", "summonHunger" }, + { "T_summon_Seducer", "summonSeducer", "T_Dae_Cre_Seduc_01", 52, "td\\s\\td_s_summ_sed.dds", "summonSeducerDesc", "summonHunger" }, + { "T_summon_SeducerDark", "summonSeducerDark", "T_Dae_Cre_SeducDark_02", 75, "td\\s\\td_s_summ_d_sed.dds", "summonSeducerDarkDesc", "summonHunger" }, + { "T_summon_Vermai", "summonVermai", "T_Dae_Cre_Verm_01", 29, "td\\s\\td_s_summ_vermai.dds", "summonVermaiDesc", "summonHunger" }, + { "T_summon_AtroStormMon", "summonStormMonarch", "T_Dae_Cre_MonarchSt_01", 60, "td\\s\\td_s_sum_stm_monch.dds", "summonStormMonarchDesc", "summonHunger" }, + { "T_summon_IceWraith", "summonIceWraith", "T_Sky_Cre_IceWr_01", 35, "td\\s\\td_s_sum_ice_wrth.dds", "summonIceWraithDesc", "callBear" }, + { "T_summon_DweSpectre", "summonDweSpectre", "dwarven ghost", 17, "td\\s\\td_s_sum_dwe_spctre.dds", "summonDweSpectreDesc", "summonAncestralGhost" }, + { "T_summon_SteamCent", "summonSteamCent", "centurion_steam", 29, "td\\s\\td_s_sum_dwe_cent.dds", "summonSteamCentDesc", "summonCenturionSphere" }, + { "T_summon_SpiderCent", "summonSpiderCent", "centurion_spider", 15, "td\\s\\td_s_sum_dwe_spdr.dds", "summonSpiderCentDesc", "summonCenturionSphere" }, + { "T_summon_WelkyndSpirit", "summonWelkyndSpirit", "T_Ayl_Cre_WelkSpr_01", 29, "td\\s\\td_s_sum_welk_srt.dds", "summonWelkyndSpiritDesc", "callBear" }, + { "T_summon_Auroran", "summonAuroran", "T_Dae_Cre_Auroran_01", 46, "td\\s\\td_s_sum_auro.dds", "summonAuroranDesc", "summonHunger" }, + { "T_summon_Herne", "summonHerne", "T_Dae_Cre_Herne_01", 18, "td\\s\\td_s_sum_herne.dds", "summonHerneDesc", "summonHunger" }, + { "T_summon_Morphoid", "summonMorphoid", "T_Dae_Cre_Morphoid_01", 21, "td\\s\\td_s_sum_morph.dds", "summonMorphoidDesc", "summonHunger" }, + { "T_summon_Draugr", "summonDraugr", "T_Sky_Und_Drgr_01", 29, "td\\s\\td_s_sum_draugr.dds", "summonDraugrDesc", "summonAncestralGhost" }, + { "T_summon_Spriggan", "summonSpriggan", "T_Sky_Cre_Spriggan_01", 48, "td\\s\\td_s_sum_sprig.dds", "summonSprigganDesc", "callBear" }, + { "T_summon_BoneldGr", "summonGreaterBonelord", "T_Mw_Und_BoneldGr_01", 71, "td\\s\\td_s_sum_gtr_bnlrd.dds", "summonGreaterBonelordDesc", "summonAncestralGhost" }, + { "T_summon_Ghost", "summonGhost", "T_Cyr_Und_Ghst_01", 7, "td\\s\\td_s_summ_ghost.dds", "summonGhostDesc", "summonAncestralGhost" }, + { "T_summon_Wraith", "summonWraith", "T_Cyr_Und_Wrth_01", 49, "td\\s\\td_s_summ_wraith.dds", "summonWraithDesc", "summonAncestralGhost" }, + { "T_summon_Barrowguard", "summonBarrowguard", "T_Cyr_Und_Mum_01", 11, "td\\s\\td_s_summ_brwgurd.dds", "summonBarrowguardDesc", "summonAncestralGhost" }, + { "T_summon_MinoBarrowguard", "summonMinoBarrowguard", "T_Cyr_Und_MinoBarrow_01", 57, "td\\s\\td_s_summ_mintur.dds", "summonMinoBarrowguardDesc", "summonAncestralGhost" }, + { "T_summon_SkeletonChampion", "summonSkeletonChampion", "T_Glb_Und_SkelCmpGls_01", 32, "td\\s\\td_s_sum_skele_c.dds", "summonSkeletonChampionDesc", "summonAncestralGhost" }, + { "T_summon_AtroFrostMon", "summonFrostMonarch", "T_Dae_Cre_MonarchFr_01", 47, "td\\s\\td_s_sum_fst_monch.dds", "summonFrostMonarchDesc", "summonHunger" }, + { "T_summon_SpiderDaedra", "summonSpiderDaedra", "T_Dae_Cre_SpiderDae_01", 42, "td\\s\\td_s_sum_spidr_dae.dds", "summonSpiderDaedraDesc", "summonHunger" }, } -- effect id, effect name, item id, 2nd item ID, effect mana cost, icon, effect description diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 75040a6..7db6d76 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -23,59 +23,59 @@ TamrielData_magic_passwallExterior: "You must be in a confined space." TamrielData_magic_passwallDoorExterior: "You cannot leave a confined space." TamrielData_magic_passwallUnderwater: "You cannot be underwater." -Magic_T_summon_Devourer: "Summon Devourer" -Magic_T_summon_DevourerDesc: "This effect summons a devourer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_DremArch: "Summon Dremora Archer" -Magic_T_summon_DremArchDesc: "This effect summons a dremora archer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_DremCast: "Summon Dremora Spellcaster" -Magic_T_summon_DremCastDesc: "This effect summons a dremora spellcaster from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Guardian: "Summon Guardian" -Magic_T_summon_GuardianDesc: "This effect summons a guardian from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_LesserClfr: "Summon Rock Biter Clannfear" -Magic_T_summon_LesserClfrDesc: "This effect summons a rock biter clannfear from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Ogrim: "Summon Ogrim" -Magic_T_summon_OgrimDesc: "This effect summons an ogrim from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Seducer: "Summon Seducer" -Magic_T_summon_SeducerDesc: "This effect summons a seducer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_SeducerDark: "Summon Dark Seducer" -Magic_T_summon_SeducerDarkDesc: "This effect summons a dark seducer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Vermai: "Summon Vermai" -Magic_T_summon_VermaiDesc: "This effect summons a vermai from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_AtroStormMon: "Summon Storm Monarch" -Magic_T_summon_AtroStormMonDesc: "This effect summons a storm monarch from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_IceWraith: "Summon Ice Wraith" -Magic_T_summon_IceWraithDesc: "This effect summons an ice wraith from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_DweSpectre: "Summon Dwarven Spectre" -Magic_T_summon_DweSpectreDesc: "This effect summons a dwarven spectre from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_SteamCent: "Summon Steam Centurion" -Magic_T_summon_SteamCentDesc: "This effect summons an steam centurion from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_SpiderCent: "Summon Centurion Spider" -Magic_T_summon_SpiderCentDesc: "This effect summons a centurion spider from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_WelkyndSpirit: "Summon Welkynd Spirit" -Magic_T_summon_WelkyndSpiritDesc: "This effect summons a welkynd spirit from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Auroran: "Summon Auroran" -Magic_T_summon_AuroranDesc: "This effect summons an auroran from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Herne: "Summon Herne" -Magic_T_summon_HerneDesc: "This effect summons a herne from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Morphoid: "Summon Morphoid Daedra" -Magic_T_summon_MorphoidDesc: "This effect summons a morphoid daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Draugr: "Summon Draugr" -Magic_T_summon_DraugrDesc: "This effect summons a draugr from the Underworld. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Underworld. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Spriggan: "Summon Spriggan" -Magic_T_summon_SprigganDesc: "This effect summons a spriggan from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_BoneldGr: "Summon Bonelord Warder" -Magic_T_summon_BoneldGrDesc: "This effect summons a bonelord warder from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Ghost: "Summon Ghost" -Magic_T_summon_GhostDesc: "This effect summons a ghost from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Wraith: "Summon Wraith" -Magic_T_summon_WraithDesc: "This effect summons a wraith from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_Barrowguard: "Summon Barrowguard" -Magic_T_summon_BarrowguardDesc: "This effect summons a barrowguard from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_MinoBarrowguard: "Summon Minotaur Barrowguard" -Magic_T_summon_MinoBarrowguardDesc: "This effect summons a minotaur barrowguard from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_SkeletonChampion: "Summon Skeleton Champion" -Magic_T_summon_SkeletonChampionDesc: "This effect summons a skeleton champion from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_AtroFrostMon: "Summon Frost Monarch" -Magic_T_summon_AtroFrostMonDesc: "This effect summons a frost monarch from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." -Magic_T_summon_SpiderDaedra: "Summon Spider Daedra" -Magic_T_summon_SpiderDaedraDesc: "This effect summons a spider daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonDevourer: "Summon Devourer" +Magic_summonDevourerDesc: "This effect summons a devourer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonDremoraArcher: "Summon Dremora Archer" +Magic_summonDremoraArcherDesc: "This effect summons a dremora archer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonDremoraCaster: "Summon Dremora Spellcaster" +Magic_summonDremoraCasterDesc: "This effect summons a dremora spellcaster from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonGuardian: "Summon Guardian" +Magic_summonGuardianDesc: "This effect summons a guardian from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonLesserClannfear: "Summon Rock Biter Clannfear" +Magic_summonLesserClannfearDesc: "This effect summons a rock biter clannfear from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonOgrim: "Summon Ogrim" +Magic_summonOgrimDesc: "This effect summons an ogrim from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonSeducer: "Summon Seducer" +Magic_summonSeducerDesc: "This effect summons a seducer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonSeducerDark: "Summon Dark Seducer" +Magic_summonSeducerDarkDesc: "This effect summons a dark seducer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonVermai: "Summon Vermai" +Magic_summonVermaiDesc: "This effect summons a vermai from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonStormMonarch: "Summon Storm Monarch" +Magic_summonStormMonarchDesc: "This effect summons a storm monarch from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonIceWraith: "Summon Ice Wraith" +Magic_summonIceWraithDesc: "This effect summons an ice wraith from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonDweSpectre: "Summon Dwarven Spectre" +Magic_summonDweSpectreDesc: "This effect summons a dwarven spectre from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonSteamCent: "Summon Steam Centurion" +Magic_summonSteamCentDesc: "This effect summons an steam centurion from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonSpiderCent: "Summon Centurion Spider" +Magic_summonSpiderCentDesc: "This effect summons a centurion spider from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonWelkyndSpirit: "Summon Welkynd Spirit" +Magic_summonWelkyndSpiritDesc: "This effect summons a welkynd spirit from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonAuroran: "Summon Auroran" +Magic_summonAuroranDesc: "This effect summons an auroran from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonHerne: "Summon Herne" +Magic_summonHerneDesc: "This effect summons a herne from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonMorphoid: "Summon Morphoid Daedra" +Magic_summonMorphoidDesc: "This effect summons a morphoid daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonDraugr: "Summon Draugr" +Magic_summonDraugrDesc: "This effect summons a draugr from the Underworld. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Underworld. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonSpriggan: "Summon Spriggan" +Magic_summonSprigganDesc: "This effect summons a spriggan from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonGreaterBonelord: "Summon Bonelord Warder" +Magic_summonGreaterBonelordDesc: "This effect summons a bonelord warder from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonGhost: "Summon Ghost" +Magic_summonGhostDesc: "This effect summons a ghost from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonWraith: "Summon Wraith" +Magic_summonWraithDesc: "This effect summons a wraith from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonBarrowguard: "Summon Barrowguard" +Magic_summonBarrowguardDesc: "This effect summons a barrowguard from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonMinoBarrowguard: "Summon Minotaur Barrowguard" +Magic_summonMinoBarrowguardDesc: "This effect summons a minotaur barrowguard from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonSkeletonChampion: "Summon Skeleton Champion" +Magic_summonSkeletonChampionDesc: "This effect summons a skeleton champion from the Outer Realms. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to the Outer Realms. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonFrostMonarch: "Summon Frost Monarch" +Magic_summonFrostMonarchDesc: "This effect summons a frost monarch from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_summonSpiderDaedra: "Summon Spider Daedra" +Magic_summonSpiderDaedraDesc: "This effect summons a spider daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index b08c38a..1a53f26 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -21,4 +21,61 @@ TamrielData_magic_passwallWard: "Vous ne pouvez pas passer jusque-là." TamrielData_magic_passwallAlpha: "Vous ne pouvez pas passer à travers ceci." TamrielData_magic_passwallExterior: "Vous devez vous trouver dans un espace confiné." TamrielData_magic_passwallDoorExterior: "Vous ne pouvez pas quitter un espace confiné." -TamrielData_magic_passwallUnderwater: "Vous ne pouvez pas vous trouver sous l'eau." \ No newline at end of file +TamrielData_magic_passwallUnderwater: "Vous ne pouvez pas vous trouver sous l'eau." + +Magic_summonDevourer: "Appel de consummeur" +Magic_summonDevourerDesc: "Cet effet permet d'invoquer un consummeur des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonDremoraArcher: "Appel d'archer drémora" +Magic_summonDremoraArcherDesc: "Cet effet permet d'invoquer un archer drémora des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonDremoraCaster: "Appel lanceur de sorts drémora" +Magic_summonDremoraCasterDesc: "Cet effet permet d'invoquer un lanceur de sorts drémora des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonGuardian: "Appel de gardien" +Magic_summonGuardianDesc: "Cet effet permet d'invoquer un gardien des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonLesserClannfear: "Appel faucheclan mange-rocher" +Magic_summonLesserClannfearDesc: "Cet effet permet d'invoquer un faucheclan mange-rocher des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonOgrim: "Appel d'ogrim" +Magic_summonOgrimDesc: "Cet effet permet d'invoquer un ogrim des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonSeducer: "Appel de séductrice" +Magic_summonSeducerDesc: "Cet effet permet d'invoquer une séductrice des Royaumes extérieurs. Elle apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'elle soit tuée. Quand elle meurt ou que le sort prend fin, elle disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonSeducerDark: "Appel de sombre séductrice" +Magic_summonSeducerDarkDesc: "Cet effet permet d'invoquer une sombre séductrice des Royaumes extérieurs. Elle apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'elle soit tuée. Quand elle meurt ou que le sort prend fin, elle disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonVermai: "Appel de vermaï" +Magic_summonVermaiDesc: "Cet effet permet d'invoquer un vermaï des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonStormMonarch: "Appel de monarque des tempêtes" +Magic_summonStormMonarchDesc: "Cet effet permet d'invoquer un monarque des tempêtes des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonIceWraith: "Appel de spectre des glaces" +Magic_summonIceWraithDesc: "Cet effet permet d'invoquer un spectre des glaces des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonDweSpectre: "Appel de spectre dwemer" +Magic_summonDweSpectreDesc: "Cet effet permet d'invoquer un spectre dwemer des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonSteamCent: "Appel de centurion à vapeur" +Magic_summonSteamCentDesc: "Cet effet permet d'invoquer un centurion à vapeur des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonSpiderCent: "Appel d'araignée-centurion" +Magic_summonSpiderCentDesc: "Cet effet permet d'invoquer une araignée-centurion des Royaumes extérieurs. Elle apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'elle soit tuée. Quand elle meurt ou que le sort prend fin, elle disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonWelkyndSpirit: "Appel d'esprit de Welkynd" +Magic_summonWelkyndSpiritDesc: "Cet effet permet d'invoquer un esprit de Welkynd des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonAuroran: "Appel d'aurorien" +Magic_summonAuroranDesc: "Cet effet permet d'invoquer un aurorien des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonHerne: "Appel d'herne" +Magic_summonHerneDesc: "Cet effet permet d'invoquer un herne des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonMorphoid: "Appel de Daedra morphoïde" +Magic_summonMorphoidDesc: "Cet effet permet d'invoquer un Daedra morphoïde des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonDraugr: "Appel de draugr" +Magic_summonDraugrDesc: "Cet effet permet d'invoquer un draugr des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonSpriggan: "Appel de spriggane" +Magic_summonSprigganDesc: "Cet effet permet d'invoquer une spriggane des Royaumes extérieurs. Elle apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'elle soit tuée. Quand elle meurt ou que le sort prend fin, elle disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonGreaterBonelord: "Appel gardien seigneur ossement" +Magic_summonGreaterBonelordDesc: "Cet effet permet d'invoquer un gardien seigneur des ossements des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonGhost: "Appel de fantôme" +Magic_summonGhostDesc: "Cet effet permet d'invoquer un fantôme des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonWraith: "Appel de spectre" +Magic_summonWraithDesc: "Cet effet permet d'invoquer un spectre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonBarrowguard: "Appel de gardien de tertre" +Magic_summonBarrowguardDesc: "Cet effet permet d'invoquer un gardien de tertre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonMinoBarrowguard: "Appel minotaure gardien tertre" +Magic_summonMinoBarrowguardDesc: "Cet effet permet d'invoquer un minotaure gardien de tertre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonSkeletonChampion: "Appel de squelette champion" +Magic_summonSkeletonChampionDesc: "Cet effet permet d'invoquer un squelette champion des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonFrostMonarch: "Appel de monarque de givre" +Magic_summonFrostMonarchDesc: "Cet effet permet d'invoquer un monarque de givre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_summonSpiderDaedra: "Appel d'araignée daedra" +Magic_summonSpiderDaedraDesc: "Cet effet permet d'invoquer un monarque de givre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." diff --git a/Data Files/l10n/TamrielData/pl.yaml b/Data Files/l10n/TamrielData/pl.yaml index f82947c..574370d 100644 --- a/Data Files/l10n/TamrielData/pl.yaml +++ b/Data Files/l10n/TamrielData/pl.yaml @@ -1 +1,58 @@ -TamrielData_main_imgaHelm: "Samce Imga nie mogą nosić hełmów." \ No newline at end of file +TamrielData_main_imgaHelm: "Samce Imga nie mogą nosić hełmów." + +Magic_summonDevourer: "Przywołanie Pożeracza" +Magic_summonDevourerDesc: "Przywołuje z Otchłani pożeracza. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonDremoraArcher: "Przywołanie Dremory-Łucznika" +Magic_summonDremoraArcherDesc: "Przywołuje z Otchłani Dremorę-łucznika. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonDremoraCaster: "Przywołanie Dremory-Czarodzieja" +Magic_summonDremoraCasterDesc: "Przywołuje z Otchłani Dremorę-czarodzieja. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonGuardian: "Przywołanie Stróża" +Magic_summonGuardianDesc: "Przywołuje z Otchłani stróża. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonLesserClannfear: "Przewołanie Postrachu Klanów-Głazożera" +Magic_summonLesserClannfearDesc: "Przywołuje z Otchłani postrach klanów-głazożera. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonOgrim: "Przywołanie Ogrima" +Magic_summonOgrimDesc: "Przywołuje z Otchłani ogrima. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonSeducer: "Przywołanie Uwodzicielki" +Magic_summonSeducerDesc: "Przywołuje z Otchłani uwodzicielkę. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonSeducerDark: "Przywołanie Mrocznej Uwodzicielki" +Magic_summonSeducerDarkDesc: "Przywołuje z Otchłani mroczną uwodzicielkę. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonVermai: "Przywołanie Vermai" +Magic_summonVermaiDesc: "Przywołuje z Otchłani vermai. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonStormMonarch: "Przywołanie Monarchy Burz" +Magic_summonStormMonarchDesc: "Przywołuje z Otchłani monarchę burz. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonIceWraith: "Przywołanie Lodowego Upiora" +Magic_summonIceWraithDesc: "Przywołuje lodowego upiora. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonDweSpectre: "Przywołanie Krasnoludzkiego Widma" +Magic_summonDweSpectreDesc: "Przywołuje krasnoludzkie widmo. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonSteamCent: "Przywołanie Parowego Centuriona" +Magic_summonSteamCentDesc: "Przywołuje parowego centuriona. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonSpiderCent: "Przywołanie Pajęczego Centuriona" +Magic_summonSpiderCentDesc: "Przywołuje pajęczego centuriona. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonWelkyndSpirit: "Przywołanie Zjawy Welkynd" +Magic_summonWelkyndSpiritDesc: "Przywołuje zjawę welkynd. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonAuroran: "Przywołanie Aurorana" +Magic_summonAuroranDesc: "Przywołuje z Otchłani aurorana. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonHerne: "Przywołanie Herna" +Magic_summonHerneDesc: "Przywołuje z Otchłani herna. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonMorphoid: "Przywołanie Morphoida" +Magic_summonMorphoidDesc: "Przywołuje z Otchłani morphoida. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonDraugr: "Przywołanie Draugra" +Magic_summonDraugrDesc: "Przywołuje draugra. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonSpriggan: "Przywołanie Spriggana" +Magic_summonSprigganDesc: "Przywołuje spriggana. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonGreaterBonelord: "Przywołanie Potężniejszego Kościeja" +Magic_summonGreaterBonelordDesc: "Przywołuje potężniejszego kościeja. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonGhost: "Przywołanie Ducha" +Magic_summonGhostDesc: "Przywołuje ducha. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonWraith: "Przywołanie Upiora" +Magic_summonWraithDesc: "Przywołuje upiora. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonBarrowguard: "Przywołanie Strażnika Kurhanu" +Magic_summonBarrowguardDesc: "Przywołuje strażnika kurhanu. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonMinoBarrowguard: "Przywołanie Minotaura-Strażnika Kurhanu" +Magic_summonMinoBarrowguardDesc: "Przywołuje minotaura-strażnika kurhanu. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonSkeletonChampion: "Przywołanie Szkieletu-Bohatera" +Magic_summonSkeletonChampionDesc: "Przywołuje szkielet-bohatera. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do swojego wymiaru. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonFrostMonarch: "Przywołanie Monarchy Mrozu" +Magic_summonFrostMonarchDesc: "Przywołuje z Otchłani monarchę mrozu. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_summonSpiderDaedra: "Przywołanie Pajęczej Daedry" +Magic_summonSpiderDaedraDesc: "Przywołuje z Otchłani pajęczą daedrę. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" \ No newline at end of file diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index a049e3b..3abbadd 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -8,37 +8,12 @@ local I = require('openmw.interfaces') local nearby = require('openmw.nearby') local self = require('openmw.self') local util = require('openmw.util') +local magicData = require('MWSE.mods.TamrielData.magicdata') -local summons = { - t_summon_devourer = 't_dae_cre_devourer_01', - t_summon_dremarch = 't_dae_cre_drem_arch_01', - t_summon_dremcast = 't_dae_cre_drem_cast_01', - t_summon_guardian = 't_dae_cre_guardian_01', - t_summon_lesserclfr = 't_dae_cre_lesserclfr_01', - t_summon_ogrim = 'ogrim', - t_summon_seducer = 't_dae_cre_seduc_01', - t_summon_seducerdark = 't_dae_cre_seducdark_02', - t_summon_vermai = 't_dae_cre_verm_01', - t_summon_atrostormmon = 't_dae_cre_monarchst_01', - t_summon_icewraith = 't_sky_cre_icewr_01', - t_summon_dwespectre = 'dwarven ghost', - t_summon_steamcent = 'centurion_steam', - t_summon_spidercent = 'centurion_spider', - t_summon_welkyndspirit = 't_ayl_cre_welkspr_01', - t_summon_auroran = 't_dae_cre_auroran_01', - t_summon_herne = 't_dae_cre_herne_01', - t_summon_morphoid = 't_dae_cre_morphoid_01', - t_summon_draugr = 't_sky_und_drgr_01', - t_summon_spriggan = 't_sky_cre_spriggan_01', - t_summon_boneldgr = 't_mw_und_boneldgr_01', - t_summon_ghost = 't_cyr_und_ghst_01', - t_summon_wraith = 't_cyr_und_wrth_01', - t_summon_barrowguard = 't_cyr_und_mum_01', - t_summon_minobarrowguard = 't_cyr_und_minobarrow_01', - t_summon_skeletonchampion = 't_glb_und_skelcmpgls_01', - t_summon_atrofrostmon = 't_dae_cre_monarchfr_01', - t_summon_spiderdaedra = 't_dae_cre_spiderdae_01', -} +local summons = {} +for _, values in pairs(magicData.td_summon_effects) do + summons[values[1]:lower()] = values[3] +end local state = { summons = {} diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index ed88288..98a1eb3 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -1,94 +1,65 @@ local content = require('openmw.content') local core = require('openmw.core') +local magicData = require('MWSE.mods.TamrielData.magicdata') -local summonTemplates = { - automaton = 'summoncenturionsphere', - creature = 'summonbear', - daedra = 'summonhunger', - undead = 'summonancestralghost' -} - -local summonEffects = { - T_summon_Devourer = { 52, 'td\\s\\td_s_summ_dev.dds', 'daedra' }, - T_summon_DremArch = { 33, 'td\\s\\td_s_sum_drm_arch.dds', 'daedra' }, - T_summon_DremCast = { 31, 'td\\s\\td_s_sum_drm_mage.dds', 'daedra' }, - T_summon_Guardian = { 69, 'td\\s\\td_s_sum_guard.dds', 'daedra' }, - T_summon_LesserClfr = { 19, 'td\\s\\td_s_sum_lsr_clan.dds', 'daedra' }, - T_summon_Ogrim = { 33, 'td\\s\\td_s_summ_ogrim.dds', 'daedra' }, - T_summon_Seducer = { 52, 'td\\s\\td_s_summ_sed.dds', 'daedra' }, - T_summon_SeducerDark = { 75, 'td\\s\\td_s_summ_d_sed.dds', 'daedra' }, - T_summon_Vermai = { 29, 'td\\s\\td_s_summ_vermai.dds', 'daedra' }, - T_summon_AtroStormMon = { 60, 'td\\s\\td_s_sum_stm_monch.dds', 'daedra' }, - T_summon_IceWraith = { 35, 'td\\s\\td_s_sum_ice_wrth.dds', 'undead' }, - T_summon_DweSpectre = { 17, 'td\\s\\td_s_sum_dwe_spctre.dds', 'undead' }, - T_summon_SteamCent = { 29, 'td\\s\\td_s_sum_dwe_cent.dds', 'automaton' }, - T_summon_SpiderCent = { 15, 'td\\s\\td_s_sum_dwe_spdr.dds', 'automaton' }, - T_summon_WelkyndSpirit = { 29, 'td\\s\\td_s_sum_welk_srt.dds', 'undead' }, - T_summon_Auroran = { 46, 'td\\s\\td_s_sum_auro.dds', 'daedra' }, - T_summon_Herne = { 18, 'td\\s\\td_s_sum_herne.dds', 'daedra' }, - T_summon_Morphoid = { 21, 'td\\s\\td_s_sum_morph.dds', 'daedra' }, - T_summon_Draugr = { 29, 'td\\s\\td_s_sum_draugr.dds', 'undead' }, - T_summon_Spriggan = { 48, 'td\\s\\td_s_sum_sprig.dds', 'creature' }, - T_summon_BoneldGr = { 71, 'td\\s\\td_s_sum_gtr_bnlrd.dds', 'undead' }, - T_summon_Ghost = { 7, 'td\\s\\td_s_summ_ghost.dds', 'undead' }, - T_summon_Wraith = { 49, 'td\\s\\td_s_summ_wraith.dds', 'undead' }, - T_summon_Barrowguard = { 11, 'td\\s\\td_s_summ_brwgurd.dds', 'undead' }, - T_summon_MinoBarrowguard = { 57, 'td\\s\\td_s_summ_mintur.dds', 'undead' }, - T_summon_SkeletonChampion = { 32, 'td\\s\\td_s_sum_skele_c.dds', 'undead' }, - T_summon_AtroFrostMon = { 47, 'td\\s\\td_s_sum_fst_monch.dds', 'daedra' }, - T_summon_SpiderDaedra = { 42, 'td\\s\\td_s_sum_spidr_dae.dds', 'daedra' }, -} +local l10n = core.l10n('TamrielData') -local summonSpells = { - T_Com_Cnj_SummonDevourer = { 156, 'T_summon_Devourer', 60 }, - T_Com_Cnj_SummonDremoraArcher = { 98, 'T_summon_DremArch', 60 }, - T_Com_Cnj_SummonDremoraCaster = { 93, 'T_summon_DremCast', 60 }, - T_Com_Cnj_SummonGuardian = { 155, 'T_summon_Guardian', 45 }, - T_Com_Cnj_SummonLesserClannfear = { 57, 'T_summon_LesserClfr', 60 }, - T_Com_Cnj_SummonOgrim = { 99, 'T_summon_Ogrim', 60 }, - T_Com_Cnj_SummonSeducer = { 156, 'T_summon_Seducer', 60 }, - T_Com_Cnj_SummonSeducerDark = { 169, 'T_summon_SeducerDark', 45 }, - T_Com_Cnj_SummonVermai = { 88, 'T_summon_Vermai', 60 }, - T_Com_Cnj_SummonStormMonarch = { 180, 'T_summon_AtroStormMon', 60 }, - T_Nor_Cnj_SummonIceWraith = { 105, 'T_summon_IceWraith', 60 }, - T_Dwe_Cnj_Uni_SummonDweSpectre = { 52, 'T_summon_DweSpectre', 60 }, - T_Dwe_Cnj_Uni_SummonSteamCent = { 88, 'T_summon_SteamCent', 60 }, - T_Dwe_Cnj_Uni_SummonSpiderCent = { 45, 'T_summon_SpiderCent', 60 }, - T_Ayl_Cnj_SummonWelkyndSpirit = { 78, 'T_summon_WelkyndSpirit', 60 }, - T_Com_Cnj_SummonAuroran = { 138, 'T_summon_Auroran', 60 }, - T_Com_Cnj_SummonHerne = { 54, 'T_summon_Herne', 60 }, - T_Com_Cnj_SummonMorphoid = { 63, 'T_summon_Morphoid', 60 }, - T_Nor_Cnj_SummonDraugr = { 78, 'T_summon_Draugr', 60 }, - T_Nor_Cnj_SummonSpriggan = { 144, 'T_summon_Spriggan', 60 }, - T_De_Cnj_SummonGreaterBonelord = { 160, 'T_summon_BoneldGr', 45 }, - T_Cr_Cnj_AylSorcKSummon1 = { 40, 'T_summon_Auroran', 40 }, - T_Cr_Cnj_AylSorcKSummon3 = { 25, 'T_summon_WelkyndSpirit', 40 }, - T_Cyr_Cnj_SummonWraith = { 147, 'T_summon_Wraith', 60 }, - T_Cyr_Cnj_SummonBarrowguard = { 33, 'T_summon_Barrowguard', 60 }, - T_Cyr_Cnj_SummonMinoBarrowguard = { 171, 'T_summon_MinoBarrowguard', 60 }, - T_Com_Cnj_SummonSkeletonChamp = { 96, 'T_summon_SkeletonChampion', 60 }, - T_Com_Cnj_SummonFrostMonarch = { 141, 'T_summon_AtroFrostMon', 60 }, - T_Com_Cnj_SummonSpiderDaedra = { 126, 'T_summon_SpiderDaedra', 60 }, -} +local function t(key) + if not key then + return '' + end + return l10n('Magic_' .. key) +end -local l10n = core.l10n('TamrielData') +local function replaceSpells(table) + local types = { + spell = content.spells.TYPE.Spell, + ability = content.spells.TYPE.Ability, + blight = content.spells.TYPE.Blight, + disease = content.spells.TYPE.Disease, + curse = content.spells.TYPE.Curse, + power = content.spells.TYPE.Power, + } + local range = { + self = content.RANGE.Self, + touch = content.RANGE.Touch, + target = content.RANGE.Target + } + local spells = content.spells.records + for _, values in pairs(table) do + local id = values[1] + local type = values[2] + local name = values[3] + local cost = values[4] + local effects = {} + for i = 1,8 do + local row = values[4 + i] + if not row then + break + end + effects[i] = row + row.range = range[row.range] + end + local spell = spells[id] + if cost then + spell.cost = cost + end + spell.type = types[type] + if name then + spell.name = t(name) + end + spell.effects = effects + end +end local function addSummons() local effects = content.magicEffects.records - for id, values in pairs(summonEffects) do - local cost = values[1] - local icon = values[2] - local template = values[3] - effects[id] = { template = effects[summonTemplates[template]], cost = cost, icon = icon, name = l10n('Magic_' .. id), description = l10n('Magic_' .. id .. 'Desc'), allowsSpellmaking = true, allowsEnchanting = true } - end - local spells = content.spells.records - local type = content.spells.TYPE.Spell - local range = content.RANGE.Self - for id, values in pairs(summonSpells) do - local cost = values[1] - local effect = values[2] - local duration = values[3] - spells[id] = { cost = cost, type = type, isAutocalc = false, starterSpellFlag = false, name = l10n('Magic_' .. effect), effects = { { duration = duration, id = effect, range = range } } } + for _, values in pairs(magicData.td_summon_effects) do + local id, name, creature, cost, icon, description, template = unpack(values) + if template == 'callBear' then + template = 'summonbear' + end + effects[id] = { template = effects[template], cost = cost, icon = icon, name = t(name), description = t(desc), allowsSpellmaking = true, allowsEnchanting = true } end end @@ -96,6 +67,7 @@ return { engineHandlers = { onContentFilesLoaded = function() addSummons() + replaceSpells(magicData.td_summon_spells) end } } From c0308a1a2c1fc1974f74ee9fab5ef1e6cbb16ebf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 11 Apr 2026 12:01:02 +0200 Subject: [PATCH 05/28] Improve performance --- .../scripts/TamrielData/actor_magic.lua | 100 +++++++++++------- .../scripts/TamrielData/actor_summons.lua | 3 +- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 900b122..a670a1e 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -13,11 +13,15 @@ local effectUpdateHandlers = {} local effectEndHandlers = {} local function onEffectStart(spell, effect) - auxUtil.callEventHandlers(effectStartHandlers, spell, effect) + local track = { ignore = true } + auxUtil.callEventHandlers(effectStartHandlers, spell, effect, track) + return track.ignore end local function onEffectUpdate(spell, effect) - auxUtil.callEventHandlers(effectUpdateHandlers, spell, effect) + local track = { ignore = false } + auxUtil.callEventHandlers(effectUpdateHandlers, spell, effect, track) + return track.ignore end local function onEffectEnd(id, index) @@ -27,6 +31,7 @@ end local STATE_INIT = 0 local STATE_ACTIVE = 1 local STATE_ONCE = 2 +local STATE_IGNORE = 3 local appliedOnce = {} for _, effect in pairs(core.magic.effects.records) do @@ -35,74 +40,85 @@ for _, effect in pairs(core.magic.effects.records) do end end -local state +local state = { + delayUpdateChecks = true, + spells = {} +} local activeSpells = types.Actor.activeSpells(self) -- This should all be replaced with built in OpenMW stuff, but that doesn't exist yet. This code cannot track effect lifecycles properly local function updateEffects() + state.delayUpdateChecks = true local active = {} for _, spell in pairs(activeSpells) do local id = spell.activeSpellId active[id] = true - local effects = state[id] + local effects = state.spells[id] if effects == nil then effects = {} - state[id] = effects - for _, effect in pairs(spell.effects) do - effects[effect.index] = STATE_INIT - onEffectStart(spell, effect) - end - else - local activeIndices = {} - for _, effect in pairs(spell.effects) do - local index = effect.index - activeIndices[index] = true - local s = effects[index] - if s == nil then - effects[index] = STATE_INIT - onEffectStart(spell, effect) - elseif s == STATE_INIT then - if appliedOnce[effect.id] then - effects[index] = STATE_ONCE - else - s = STATE_ACTIVE - effects[index] = s - end + state.spells[id] = effects + end + local activeIndices = {} + for _, effect in pairs(spell.effects) do + local index = effect.index + activeIndices[index] = true + local s = effects[index] + if s == nil then + effects[index] = STATE_INIT + if onEffectStart(spell, effect) then + effects[index] = STATE_IGNORE + else + state.delayUpdateChecks = false end - if s == STATE_ACTIVE then - onEffectUpdate(spell, effect) + elseif s == STATE_INIT then + if appliedOnce[effect.id] then + effects[index] = STATE_ONCE + else + s = STATE_ACTIVE + effects[index] = s end end - for index, _ in pairs(effects) do - if activeIndices[index] == nil then + if s == STATE_ACTIVE then + if onEffectUpdate(spell, effect) then + effects[index] = STATE_IGNORE + else + state.delayUpdateChecks = false + end + end + end + for index, s in pairs(effects) do + if activeIndices[index] == nil then + if s ~= STATE_IGNORE then onEffectEnd(id, index) - effects[index] = nil end + effects[index] = nil end end end - for id, effects in pairs(state) do + for id, effects in pairs(state.spells) do if active[id] == nil then for index, _ in pairs(effects) do onEffectEnd(id, index) end - state[id] = nil + state.spells[id] = nil end end end local function onInit() - state = {} for _, spell in pairs(activeSpells) do local effects = {} for _, effect in pairs(spell.effects) do - effects[effect.index] = STATE_INIT + effects[effect.index] = STATE_IGNORE end - state[spell.activeSpellId] = effects + state.spells[spell.activeSpellId] = effects end end +local MAX_WAIT = 0.25 +local waited = math.random() + return { engineHandlers = { onInit = onInit, @@ -116,7 +132,17 @@ return { state = data end end, - onUpdate = updateEffects, + onUpdate = function(dt) + if state.delayUpdateChecks then + waited = waited + dt + if waited < MAX_WAIT then + return + else + waited = waited - MAX_WAIT + end + end + updateEffects() + end, onInactive = updateEffects }, interfaceName = 'T_ActorMagic', @@ -133,7 +159,7 @@ return { end, removeEffect = function(id, index) -- This isn't possible and this implementation is slightly wrong, but it's better than nothing - local effects = state[id] + local effects = state.spells[id] if effects == nil then return end diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index 3abbadd..94eac41 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -51,7 +51,7 @@ local function getSafeSpawn() return origin end -I.T_ActorMagic.addEffectStartHandler(function(spell, effect) +I.T_ActorMagic.addEffectStartHandler(function(spell, effect, track) local creature = summons[effect.id] if creature == nil then return @@ -61,6 +61,7 @@ I.T_ActorMagic.addEffectStartHandler(function(spell, effect) local key = toKey(id, index) state.summons[key] = { id = id, index = index } core.sendGlobalEvent('T_Summon', { key = key, creature = creature, caster = self.object, position = getSafeSpawn() }) + track.ignore = false end) I.T_ActorMagic.addEffectEndHandler(function(id, index) From e4eff0f98400220a08c0e724a7a2ad942c0ac03c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 11 Apr 2026 13:37:33 +0200 Subject: [PATCH 06/28] Add setting --- Data Files/l10n/TamrielData/en.yaml | 2 ++ Data Files/l10n/TamrielData/fr.yaml | 2 ++ Data Files/l10n/TamrielData/pl.yaml | 3 +++ .../scripts/TamrielData/actor_summons.lua | 2 +- Data Files/scripts/TamrielData/load_magic.lua | 23 +++++++++++-------- .../TamrielData/utils/feature_data.lua | 6 +++++ .../TamrielData/utils/version_check.lua | 12 ++++++++-- 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 7db6d76..814a3de 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -5,6 +5,8 @@ Settings_TamrielData_page01Main_group01Main_restrictEquipment_Description: "Prev Settings_TamrielData_page01Main_group02Magic: "Magic" Settings_TamrielData_page01Main_group02Magic_miscSpells: "Add New Miscellaneous Spells" Settings_TamrielData_page01Main_group02Magic_miscSpells_Description: "Adds new spells that do not fit into usual categories." +Settings_TamrielData_page01Main_group02Magic_summoningSpells: "Add New Summoning Spells" +Settings_TamrielData_page01Main_group02Magic_summoningSpells_Description: "Adds new summoning spells using creatures added by Tamriel Data, such as Devourers, Herne, Dark Seducers, and Aurorans.\nRequires restart.\n\nDefault: On\n\n" Settings_TamrielData_page01Main_group99Misc: "Miscellaneous" Settings_TamrielData_page01Main_group99Misc_debugLogging: "Debug Logging" Settings_TamrielData_page01Main_group99Misc_debugLogging_Description: "Adds more logs, for example in F10 OpenMW logs. Enable this if you want to report an issue with the scripts, so that you can provide more information about it." diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index 1a53f26..0f9fe77 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -5,6 +5,8 @@ Settings_TamrielData_page01Main_group01Main_restrictEquipment_Description: "Emp Settings_TamrielData_page01Main_group02Magic: "Magie" Settings_TamrielData_page01Main_group02Magic_miscSpells: "Ajout de nouveaux sorts divers" Settings_TamrielData_page01Main_group02Magic_miscSpells_Description: "Ajoute de nouveaux sorts qui ne rentrent pas dans les catégories habituelles." +Settings_TamrielData_page01Main_group02Magic_summoningSpells: "Ajout de nouveaux sorts d'Invocation" +Settings_TamrielData_page01Main_group02Magic_summoningSpells_Description: "Ajoute de nouveaux sorts d'Invocation utilisant des créatures de Ressources communes de Tamriel comme les consummeurs, les hernes, les sombres séductrices, et les auroriens.\nRequiert un redémarrage.\n\nPar défaut : activé\n\n" Settings_TamrielData_page01Main_group99Misc: "Divers" Settings_TamrielData_page01Main_group99Misc_debugLogging: "Journalisation des messages de débug" Settings_TamrielData_page01Main_group99Misc_debugLogging_Description: "Ajoute plus de logs, par exemple dans les logs d'OpenMW affichés avec la touche F10. Activez cette option si vous souhaitez signaler un problème avec les scripts afin de pouvoir fournir plus d'informations à ce sujet." diff --git a/Data Files/l10n/TamrielData/pl.yaml b/Data Files/l10n/TamrielData/pl.yaml index 574370d..010359a 100644 --- a/Data Files/l10n/TamrielData/pl.yaml +++ b/Data Files/l10n/TamrielData/pl.yaml @@ -1,5 +1,8 @@ TamrielData_main_imgaHelm: "Samce Imga nie mogą nosić hełmów." +Settings_TamrielData_page01Main_group02Magic_summoningSpells: "Nowe Zaklęcia Przywołania" +Settings_TamrielData_page01Main_group02Magic_summoningSpells_Description: "Dodaje nowe zaklęcia przywołania używające istoty dodane w Tamriel Data, takie jak Pożeracze, Herny, Mroczne Uwodzicielki czy Auroranie.\nWymaga ponownego wczytania gry.\n\nDomyślnie: Wł.\n\n" + Magic_summonDevourer: "Przywołanie Pożeracza" Magic_summonDevourerDesc: "Przywołuje z Otchłani pożeracza. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" Magic_summonDremoraArcher: "Przywołanie Dremory-Łucznika" diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index 94eac41..87001d9 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -1,6 +1,6 @@ local core = require('openmw.core') -if core.API_REVISION < 125 then +if not core.magic.effects.records['T_summon_Devourer'] then return end diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 98a1eb3..521a27c 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -1,6 +1,7 @@ local content = require('openmw.content') local core = require('openmw.core') local magicData = require('MWSE.mods.TamrielData.magicdata') +local version_check = require('scripts.TamrielData.utils.version_check') local l10n = core.l10n('TamrielData') @@ -41,14 +42,16 @@ local function replaceSpells(table) row.range = range[row.range] end local spell = spells[id] - if cost then - spell.cost = cost - end - spell.type = types[type] - if name then - spell.name = t(name) + if spell then + if cost then + spell.cost = cost + end + spell.type = types[type] + if name then + spell.name = t(name) + end + spell.effects = effects end - spell.effects = effects end end @@ -66,8 +69,10 @@ end return { engineHandlers = { onContentFilesLoaded = function() - addSummons() - replaceSpells(magicData.td_summon_spells) + if version_check.isFeatureEnabled('summoningSpells') then + addSummons() + replaceSpells(magicData.td_summon_spells) + end end } } diff --git a/Data Files/scripts/TamrielData/utils/feature_data.lua b/Data Files/scripts/TamrielData/utils/feature_data.lua index fb2ff48..7ee5801 100644 --- a/Data Files/scripts/TamrielData/utils/feature_data.lua +++ b/Data Files/scripts/TamrielData/utils/feature_data.lua @@ -18,6 +18,12 @@ features["miscSpells"] = { settingsEnabledByDefault = true, settingsKey = "Settings_TamrielData_page01Main_group02Magic_miscSpells" } +features["summoningSpells"] = { + requiredLuaApi = 126, + settingsPlayerSectionStorageId = "Settings_TamrielData_page01Main_group02Magic", + settingsEnabledByDefault = true, + settingsKey = "Settings_TamrielData_page01Main_group02Magic_summoningSpells" +} features["debugLogging"] = { requiredLuaApi = 44, settingsPlayerSectionStorageId = "Settings_TamrielData_page01Main_group99Misc", diff --git a/Data Files/scripts/TamrielData/utils/version_check.lua b/Data Files/scripts/TamrielData/utils/version_check.lua index 647aee5..62debf2 100644 --- a/Data Files/scripts/TamrielData/utils/version_check.lua +++ b/Data Files/scripts/TamrielData/utils/version_check.lua @@ -9,8 +9,16 @@ function V.isFeatureSupported(featureName) end function V.isFeatureEnabled(featureName) - local featureSettingsStorage = feature_data[featureName] and storage.playerSection(feature_data[featureName].settingsPlayerSectionStorageId) - return featureSettingsStorage and featureSettingsStorage:get(feature_data[featureName].settingsKey) + local feature = feature_data[featureName] + if not feature then + return + end + local featureSettingsStorage = storage.playerSection(feature.settingsPlayerSectionStorageId) + local value = featureSettingsStorage and featureSettingsStorage:get(feature.settingsKey) + if value == nil then + return feature.settingsEnabledByDefault + end + return value end return V \ No newline at end of file From 0d154e25675443ba5713812a0e3b01c4983fc2e7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 11 Apr 2026 15:17:06 +0200 Subject: [PATCH 07/28] Support hypothetical multiplayer --- Data Files/Tamriel_Data.omwscripts | 3 +- .../scripts/TamrielData/actor_magic.lua | 169 +------------ .../scripts/TamrielData/actor_summons.lua | 67 +---- .../scripts/TamrielData/global_magic.lua | 232 ++++++++++++++++++ .../scripts/TamrielData/global_summons.lua | 68 ++++- 5 files changed, 310 insertions(+), 229 deletions(-) create mode 100644 Data Files/scripts/TamrielData/global_magic.lua diff --git a/Data Files/Tamriel_Data.omwscripts b/Data Files/Tamriel_Data.omwscripts index 00a6b50..7aeef41 100644 --- a/Data Files/Tamriel_Data.omwscripts +++ b/Data Files/Tamriel_Data.omwscripts @@ -5,7 +5,8 @@ GLOBAL: scripts/TamrielData/global_mwscript_variable.lua PLAYER: scripts/TamrielData/player_magic.lua PLAYER: scripts/TamrielData/player_restrict_equipment.lua MENU: scripts/TamrielData/menu_version_warning.lua +GLOBAL: scripts/TamrielData/global_magic.lua +GLOBAL: scripts/TamrielData/global_summons.lua PLAYER, NPC, CREATURE: scripts/TamrielData/actor_magic.lua PLAYER, NPC, CREATURE: scripts/TamrielData/actor_summons.lua -GLOBAL: scripts/TamrielData/global_summons.lua LOAD: scripts/TamrielData/load_magic.lua diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index a670a1e..bd69ee9 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -1,174 +1,9 @@ local core = require('openmw.core') -if core.API_REVISION < 125 then - return -end - -local types = require('openmw.types') -local self = require('openmw.self') -local auxUtil = require('openmw_aux.util') - -local effectStartHandlers = {} -local effectUpdateHandlers = {} -local effectEndHandlers = {} - -local function onEffectStart(spell, effect) - local track = { ignore = true } - auxUtil.callEventHandlers(effectStartHandlers, spell, effect, track) - return track.ignore -end - -local function onEffectUpdate(spell, effect) - local track = { ignore = false } - auxUtil.callEventHandlers(effectUpdateHandlers, spell, effect, track) - return track.ignore -end - -local function onEffectEnd(id, index) - auxUtil.callEventHandlers(effectEndHandlers, id, index) -end - -local STATE_INIT = 0 -local STATE_ACTIVE = 1 -local STATE_ONCE = 2 -local STATE_IGNORE = 3 - -local appliedOnce = {} -for _, effect in pairs(core.magic.effects.records) do - if effect.isAppliedOnce then - appliedOnce[effect.id] = true - end -end - -local state = { - delayUpdateChecks = true, - spells = {} -} - -local activeSpells = types.Actor.activeSpells(self) - --- This should all be replaced with built in OpenMW stuff, but that doesn't exist yet. This code cannot track effect lifecycles properly -local function updateEffects() - state.delayUpdateChecks = true - local active = {} - for _, spell in pairs(activeSpells) do - local id = spell.activeSpellId - active[id] = true - local effects = state.spells[id] - if effects == nil then - effects = {} - state.spells[id] = effects - end - local activeIndices = {} - for _, effect in pairs(spell.effects) do - local index = effect.index - activeIndices[index] = true - local s = effects[index] - if s == nil then - effects[index] = STATE_INIT - if onEffectStart(spell, effect) then - effects[index] = STATE_IGNORE - else - state.delayUpdateChecks = false - end - elseif s == STATE_INIT then - if appliedOnce[effect.id] then - effects[index] = STATE_ONCE - else - s = STATE_ACTIVE - effects[index] = s - end - end - if s == STATE_ACTIVE then - if onEffectUpdate(spell, effect) then - effects[index] = STATE_IGNORE - else - state.delayUpdateChecks = false - end - end - end - for index, s in pairs(effects) do - if activeIndices[index] == nil then - if s ~= STATE_IGNORE then - onEffectEnd(id, index) - end - effects[index] = nil - end - end - end - for id, effects in pairs(state.spells) do - if active[id] == nil then - for index, _ in pairs(effects) do - onEffectEnd(id, index) - end - state.spells[id] = nil - end - end -end - -local function onInit() - for _, spell in pairs(activeSpells) do - local effects = {} - for _, effect in pairs(spell.effects) do - effects[effect.index] = STATE_IGNORE - end - state.spells[spell.activeSpellId] = effects - end -end - -local MAX_WAIT = 0.25 -local waited = math.random() - return { engineHandlers = { - onInit = onInit, - onSave = function() - return state - end, - onLoad = function(data) - if data == nil then - onInit() - else - state = data - end - end, - onUpdate = function(dt) - if state.delayUpdateChecks then - waited = waited + dt - if waited < MAX_WAIT then - return - else - waited = waited - MAX_WAIT - end - end - updateEffects() - end, - onInactive = updateEffects - }, - interfaceName = 'T_ActorMagic', - interface = { - version = 1, - addEffectStartHandler = function(handler) - effectStartHandlers[#effectStartHandlers + 1] = handler - end, - addEffectUpdateHandler = function(handler) - effectUpdateHandlers[#effectUpdateHandlers + 1] = handler - end, - addEffectEndHandler = function(handler) - effectEndHandlers[#effectEndHandlers + 1] = handler - end, - removeEffect = function(id, index) - -- This isn't possible and this implementation is slightly wrong, but it's better than nothing - local effects = state.spells[id] - if effects == nil then - return - end - for i, _ in pairs(effects) do - if i ~= index then - return - end - end - activeSpells:remove(id) + onInactive = function() + core.sendGlobalEvent('T_ActorInactive', self.object) end } } diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index 87001d9..acfd683 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -8,20 +8,6 @@ local I = require('openmw.interfaces') local nearby = require('openmw.nearby') local self = require('openmw.self') local util = require('openmw.util') -local magicData = require('MWSE.mods.TamrielData.magicdata') - -local summons = {} -for _, values in pairs(magicData.td_summon_effects) do - summons[values[1]:lower()] = values[3] -end - -local state = { - summons = {} -} - -local function toKey(id, index) - return id .. ',' .. index -end local FRONT = 0 local BACK = 3 @@ -51,47 +37,15 @@ local function getSafeSpawn() return origin end -I.T_ActorMagic.addEffectStartHandler(function(spell, effect, track) - local creature = summons[effect.id] - if creature == nil then - return - end - local id = spell.activeSpellId - local index = effect.index - local key = toKey(id, index) - state.summons[key] = { id = id, index = index } - core.sendGlobalEvent('T_Summon', { key = key, creature = creature, caster = self.object, position = getSafeSpawn() }) - track.ignore = false -end) - -I.T_ActorMagic.addEffectEndHandler(function(id, index) - local key = toKey(id, index) - local summon = state.summons[key] - if summon == nil then - return - end - local creature = summon.creature - if creature and creature:isValid() then - core.sendGlobalEvent('T_Unsummon', { creature = creature }) - end - state.summons[key] = nil -end) +local state = {} return { eventHandlers = { - T_Summoned = function(data) - local summon = state.summons[data.key] - if summon == nil then - summon = {} - state.summons[data.key] = summon - end - summon.creature = data.creature - end, - T_SummonDied = function(data) - local summon = state.summons[data.key] - if summon ~= nil and summon.id ~= nil and summon.index ~= nil then - I.T_ActorMagic.removeEffect(summon.id, summon.index) - end + T_GetSummonPosition = function(data) + local position = getSafeSpawn() + data.position = position + data.caster = self.object + core.sendGlobalEvent('T_Summon', data) end, T_MarkSummon = function(data) state.caster = data.caster @@ -100,10 +54,7 @@ return { end, Died = function() if state.key ~= nil then - core.sendGlobalEvent('T_Unsummon', { creature = self.object }) - if state.caster:isValid() then - state.caster:sendEvent('T_SummonDied', { key = state.key }) - end + core.sendGlobalEvent('T_Unsummon', { creature = self.object, caster = state.caster, key = state.key }) end end }, @@ -112,7 +63,9 @@ return { return state end, onLoad = function(data) - state = data + if data then + state = data + end end } } diff --git a/Data Files/scripts/TamrielData/global_magic.lua b/Data Files/scripts/TamrielData/global_magic.lua new file mode 100644 index 0000000..121b998 --- /dev/null +++ b/Data Files/scripts/TamrielData/global_magic.lua @@ -0,0 +1,232 @@ +local core = require('openmw.core') + +if core.API_REVISION < 125 then + return +end + +local types = require('openmw.types') +local world = require('openmw.world') +local auxUtil = require('openmw_aux.util') + +local effectStartHandlers = {} +local effectUpdateHandlers = {} +local effectEndHandlers = {} + +local function onEffectStart(actor, spell, effect) + local track = { ignore = true } + auxUtil.callEventHandlers(effectStartHandlers, actor, spell, effect, track) + return track.ignore +end + +local function onEffectUpdate(actor, spell, effect) + local track = { ignore = false } + auxUtil.callEventHandlers(effectUpdateHandlers, actor, spell, effect, track) + return track.ignore +end + +local function onEffectEnd(actor, id, index) + auxUtil.callEventHandlers(effectEndHandlers, actor, id, index) +end + +local STATE_INIT = 0 +local STATE_ACTIVE = 1 +local STATE_ONCE = 2 +local STATE_IGNORE = 3 + +local appliedOnce = {} +for _, effect in pairs(core.magic.effects.records) do + if effect.isAppliedOnce then + appliedOnce[effect.id] = true + end +end + +local persistentState = { + actors = {} +} +local tempState = {} + +local function initActorState(actor, state) + local activeSpells = types.Actor.activeSpells(actor) + for _, spell in pairs(activeSpells) do + local effects = {} + for _, effect in pairs(spell.effects) do + effects[effect.index] = STATE_IGNORE + end + state.spells[spell.activeSpellId] = effects + end + return { + waited = math.random(), + activeSpells = activeSpells + } +end + +local function deleteActorState(actor) + local id = actor.id + persistentState.actors[id] = nil + tempState[id] = nil +end + +local function getActorState(actor, init) + local id = actor.id + local state = persistentState.actors[id] + if not state then + if not init then + return nil + end + state = { + delayUpdateChecks = true, + spells = {} + } + persistentState.actors[id] = state + tempState[id] = initActorState(actor, state) + elseif not tempState[id] then + tempState[id] = { + waited = math.random(), + activeSpells = types.Actor.activeSpells(actor) + } + end + return state, tempState[id] +end + +-- This should all be replaced with built in OpenMW stuff, but that doesn't exist yet. This code cannot track effect lifecycles properly +local function updateEffects(actor, state, tempState) + local canDiscard = true + state.delayUpdateChecks = true + local active = {} + for _, spell in pairs(tempState.activeSpells) do + local id = spell.activeSpellId + active[id] = true + local effects = state.spells[id] + if effects == nil then + effects = {} + state.spells[id] = effects + end + local activeIndices = {} + for _, effect in pairs(spell.effects) do + local index = effect.index + activeIndices[index] = true + local s = effects[index] + if s == nil then + effects[index] = STATE_INIT + if onEffectStart(actor, spell, effect) then + effects[index] = STATE_IGNORE + else + state.delayUpdateChecks = false + canDiscard = false + end + elseif s == STATE_INIT then + if appliedOnce[effect.id] then + effects[index] = STATE_ONCE + canDiscard = false + else + s = STATE_ACTIVE + effects[index] = s + end + end + if s == STATE_ACTIVE then + if onEffectUpdate(actor, spell, effect) then + effects[index] = STATE_IGNORE + else + state.delayUpdateChecks = false + canDiscard = false + end + end + end + for index, s in pairs(effects) do + if activeIndices[index] == nil then + if s ~= STATE_IGNORE then + onEffectEnd(actor, id, index) + end + effects[index] = nil + end + end + end + for id, effects in pairs(state.spells) do + if active[id] == nil then + for index, s in pairs(effects) do + if s ~= STATE_IGNORE then + onEffectEnd(actor, id, index) + end + end + state.spells[id] = nil + end + end + return canDiscard +end + +local MAX_WAIT = 0.25 + +local function waitOrUpdate(actor, dt) + local state, tempState = getActorState(actor, true) + if state.delayUpdateChecks then + tempState.waited = tempState.waited + dt + if tempState.waited < MAX_WAIT then + return + else + tempState.waited = tempState.waited - MAX_WAIT + end + end + updateEffects(actor, state, tempState) +end + +local activeActors = world.activeActors + +return { + engineHandlers = { + onSave = function() + return persistentState + end, + onLoad = function(data) + if data then + persistentState = data + end + end, + onUpdate = function(dt) + for _, actor in pairs(activeActors) do + waitOrUpdate(actor, dt) + end + end + }, + eventHandlers = { + T_ActorInactive = function(actor) + local state, tempState = getActorState(actor, false) + if not state then + return + end + local discard = updateEffects(actor, state, tempState) + if discard then + deleteActorState(actor) + end + end + }, + interfaceName = 'T_ActorMagic', + interface = { + version = 1, + addEffectStartHandler = function(handler) + effectStartHandlers[#effectStartHandlers + 1] = handler + end, + addEffectUpdateHandler = function(handler) + effectUpdateHandlers[#effectUpdateHandlers + 1] = handler + end, + addEffectEndHandler = function(handler) + effectEndHandlers[#effectEndHandlers + 1] = handler + end, + removeEffect = function(actor, id, index) + -- This isn't possible and this implementation is slightly wrong, but it's better than nothing + local state, tempState = getActorState(actor, false) + if not state then + return + end + local effects = state.spells[id] + if effects == nil then + return + end + for i, _ in pairs(effects) do + if i ~= index then + return + end + end + tempState.activeSpells:remove(id) + end + } +} diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index 12c2a3c..bbe39f5 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -1,23 +1,83 @@ +local core = require('openmw.core') + +if not core.magic.effects.records['T_summon_Devourer'] then + return +end + +local I = require('openmw.interfaces') local types = require('openmw.types') local world = require('openmw.world') +local magicData = require('MWSE.mods.TamrielData.magicdata') +local summons = {} +for _, values in pairs(magicData.td_summon_effects) do + summons[values[1]:lower()] = values[3] +end local startVfx = types.Static.records['VFX_Summon_Start'].model local endVfx = types.Static.records['VFX_Summon_End'].model +local state = { + summons = {} +} + +local function toKey(actor, id, index) + return actor.id .. ',' .. id .. ',' .. index +end + +I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) + local creature = summons[effect.id] + if not creature then + return + end + local id = spell.activeSpellId + local index = effect.index + local key = toKey(caster, id, index) + state.summons[key] = { id = id, index = index, creatureId = creature } + caster:sendEvent('T_GetSummonPosition', { key = key }) + track.ignore = false +end) + +local function unsummon(creature) + creature.enabled = false + world.vfx.spawn(startVfx, creature.position) +end + +I.T_ActorMagic.addEffectEndHandler(function(actor, id, index) + local key = toKey(actor, id, index) + local summon = state.summons[key] + if not summon then + return + end + local creature = summon.creature + if creature and creature:isValid() then + unsummon(creature) + end + state.summons[key] = nil +end) + return { eventHandlers = { T_Summon = function(data) - local creature = world.createObject(data.creature) + local effect = state.summons[data.key] + if not effect then + return + end + local creature = world.createObject(effect.creatureId) local caster = data.caster creature:teleport(caster.cell.name, data.position, { onGround = true }) creature:sendEvent('StartAIPackage', { type = 'Follow', target = caster }) creature:sendEvent('T_MarkSummon', { key = data.key, caster = caster }) creature:sendEvent('AddVfx', { model = startVfx }) - caster:sendEvent('T_Summoned', { key = data.key, creature = creature }) + effect.creatureId = nil + effect.creature = creature end, T_Unsummon = function(data) - data.creature.enabled = false - world.vfx.spawn(startVfx, data.creature.position) + unsummon(data.creature) + local effect = state.summons[data.key] + if effect then + state.summons[data.key] = nil + I.T_ActorMagic.removeEffect(data.caster, effect.id, effect.index) + end end } } From e8cbe1e053a8ab260c9894e0cb0dc9af57e66cbb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 11 Apr 2026 18:10:44 +0200 Subject: [PATCH 08/28] Implement enchantment, potion, ingredient, and item changing --- Data Files/l10n/TamrielData/en.yaml | 18 +++ Data Files/l10n/TamrielData/fr.yaml | 18 +++ Data Files/l10n/TamrielData/pl.yaml | 20 ++- Data Files/scripts/TamrielData/load_magic.lua | 138 ++++++++++++++++-- .../TamrielData/utils/feature_data.lua | 2 +- 5 files changed, 178 insertions(+), 18 deletions(-) diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 814a3de..8dbd6de 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -81,3 +81,21 @@ Magic_summonFrostMonarch: "Summon Frost Monarch" Magic_summonFrostMonarchDesc: "This effect summons a frost monarch from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." Magic_summonSpiderDaedra: "Summon Spider Daedra" Magic_summonSpiderDaedraDesc: "This effect summons a spider daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." + +Magic_itemPotionReflectDamageB: "Bargain Potion of Reflect Dmg" +Magic_itemPotionReflectDamageC: "Cheap Potion of Reflect Dmg" +Magic_itemPotionReflectDamageS: "Standard Potion of Reflect Dmg" +Magic_itemPotionReflectDamageQ: "Quality Potion of Reflect Dmg" +Magic_itemPotionReflectDamageE: "Exclusive Potion of Reflect Dmg" +Magic_itemPotionInsightB: "Bargain Potion of Insight" +Magic_itemPotionInsightC: "Cheap Potion of Insight" +Magic_itemPotionInsightS: "Standard Potion of Insight" +Magic_itemPotionInsightQ: "Quality Potion of Insight" +Magic_itemPotionInsightE: "Exclusive Potion of Insight" +Magic_itemPotionDetectHumanoid: "Potion of Detect Humanoid" +Magic_itemPotionDetectEnemy: "Potion of Detect Enemies" +Magic_itemPotionDetectInvisibility: "Potion of Detect Invisibility" + +Magic_itemScSummonDremoraArcher: "Scroll of Mehrunes' Quarry" +Magic_itemScSummonDremoraCaster: "Scroll of The Razor Compact" +Magic_itemScKynesIntervention: "Scroll of Kyne's Intervention" diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index 0f9fe77..d424da7 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -81,3 +81,21 @@ Magic_summonFrostMonarch: "Appel de monarque de givre" Magic_summonFrostMonarchDesc: "Cet effet permet d'invoquer un monarque de givre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." Magic_summonSpiderDaedra: "Appel d'araignée daedra" Magic_summonSpiderDaedraDesc: "Cet effet permet d'invoquer un monarque de givre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." + +Magic_itemPotionReflectDamageB: "Potion Réflexion dégâts bradée" +Magic_itemPotionReflectDamageC: "Potion Réflexion dégâts bas prix" +Magic_itemPotionReflectDamageS: "Potion Réflexion dégâts std" +Magic_itemPotionReflectDamageQ: "Potion Réflexion dégâts qualité" +Magic_itemPotionReflectDamageE: "Potion Réflexion dégâts choix" +Magic_itemPotionInsightB: "Pot. Perspicacité bradée" +Magic_itemPotionInsightC: "Pot. Perspicacité à bas prix" +Magic_itemPotionInsightS: "Pot. Perspicacité standard" +Magic_itemPotionInsightQ: "Pot. Perspicacité de qualité" +Magic_itemPotionInsightE: "Pot. Perspicacité de choix" +Magic_itemPotionDetectHumanoid: "Potion Détecte-humanoïdes" +Magic_itemPotionDetectEnemy: "Potion Détecte-ennemis" +Magic_itemPotionDetectInvisibility: "Potion Détecte-invisibilité" + +Magic_itemScSummonDremoraArcher: "Parch. de proie de Mérunès" +Magic_itemScSummonDremoraCaster: "Parch. de convention du Rasoir" +Magic_itemScKynesIntervention: "Parch. d'Intervention de Kyne" diff --git a/Data Files/l10n/TamrielData/pl.yaml b/Data Files/l10n/TamrielData/pl.yaml index 010359a..a4f06c8 100644 --- a/Data Files/l10n/TamrielData/pl.yaml +++ b/Data Files/l10n/TamrielData/pl.yaml @@ -58,4 +58,22 @@ Magic_summonSkeletonChampionDesc: "Przywołuje szkielet-bohatera. Stworzenie poj Magic_summonFrostMonarch: "Przywołanie Monarchy Mrozu" Magic_summonFrostMonarchDesc: "Przywołuje z Otchłani monarchę mrozu. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" Magic_summonSpiderDaedra: "Przywołanie Pajęczej Daedry" -Magic_summonSpiderDaedraDesc: "Przywołuje z Otchłani pajęczą daedrę. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" \ No newline at end of file +Magic_summonSpiderDaedraDesc: "Przywołuje z Otchłani pajęczą daedrę. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" + +Magic_itemPotionReflectDamageB: "Słaba mikstura odbicia obrażeń" +Magic_itemPotionReflectDamageC: "Mała mikstura odbicia obrażeń" +Magic_itemPotionReflectDamageS: "Klasyczne odbicie obrażeń" +Magic_itemPotionReflectDamageQ: "Duża mikstura odbicia obrażeń" +Magic_itemPotionReflectDamageE: "Dosk. mikstura odbicia obrażeń" +Magic_itemPotionInsightB: "Słaba mikstura intuicji" +Magic_itemPotionInsightC: "Mała mikstura intuicji" +Magic_itemPotionInsightS: "Klasyczna mikstura intuicji" +Magic_itemPotionInsightQ: "Duża mikstura intuicji" +Magic_itemPotionInsightE: "Dosk. mikstura intuicji" +Magic_itemPotionDetectHumanoid: "Mikstura wykrycia humanoidów" +Magic_itemPotionDetectEnemy: "Mikstura wykrycia przeciwników" +Magic_itemPotionDetectInvisibility: "Miks. wykrycia niewidzialności" + +Magic_itemScSummonDremoraArcher: "Zwój Pastwy Mehrunesa" +Magic_itemScSummonDremoraCaster: "Zwój Paktu Brzytwy" +Magic_itemScKynesIntervention: "Zwój Interwencji Kyne" diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 521a27c..0eeb72f 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -12,6 +12,31 @@ local function t(key) return l10n('Magic_' .. key) end +local implementedEffects = {} +local RANGE = { + self = content.RANGE.Self, + touch = content.RANGE.Touch, + target = content.RANGE.Target +} + +local function parseEffects(values, offset) + local effects = {} + local implemented = true + for i = 1,8 do + local row = values[offset + i] + if not row then + break + end + if not implementedEffects[row.id] then + implemented = false + break + end + effects[i] = row + row.range = RANGE[row.range] + end + return implemented, effects +end + local function replaceSpells(table) local types = { spell = content.spells.TYPE.Spell, @@ -21,28 +46,15 @@ local function replaceSpells(table) curse = content.spells.TYPE.Curse, power = content.spells.TYPE.Power, } - local range = { - self = content.RANGE.Self, - touch = content.RANGE.Touch, - target = content.RANGE.Target - } local spells = content.spells.records for _, values in pairs(table) do local id = values[1] local type = values[2] local name = values[3] local cost = values[4] - local effects = {} - for i = 1,8 do - local row = values[4 + i] - if not row then - break - end - effects[i] = row - row.range = range[row.range] - end + local implemented, effects = parseEffects(values, 4) local spell = spells[id] - if spell then + if spell and implemented then if cost then spell.cost = cost end @@ -55,6 +67,95 @@ local function replaceSpells(table) end end +local modifiedEnchantments = {} + +local function replaceEnchantments(table) + local types = { + castOnce = content.enchantments.TYPE.CastOnce, + onStrike = content.enchantments.TYPE.CastOnStrike, + onUse = content.enchantments.TYPE.CastOnUse, + constant = content.enchantments.TYPE.ConstantEffect, + } + local enchantments = content.enchantments.records + for _, values in pairs(table) do + local id = values[1] + local type = values[2] + local implemented, effects = parseEffects(values, 2) + local enchantment = enchantments[id] + if enchantment and implemented then + modifiedEnchantments[enchantment.id] = true + enchantment.type = types[type] + enchantment.effects = effects + end + end +end + +local function replacePotions(table) + local potions = content.potions.records + for _, values in pairs(table) do + local id = values[1] + local name = values[2] + local implemented, effects = parseEffects(values, 2) + local potion = potions[id] + if potion and implemented then + if name then + potion.name = t(name) + end + potion.effects = effects + end + end +end + +local enchantableTypes = { 'armor', 'books', 'clothes', 'weapons' } + +local function getItem(id) + for _, type in pairs(enchantableTypes) do + local c = content[type] + if c then + local item = c.records[id] + if item then + return item + end + end + end +end + +local function editItems(table) + for _, values in pairs(table) do + local id = values[1] + local name = values[2] + local value = values[3] + local item = getItem(id) + if item and modifiedEnchantments[item.enchant] then + if name then + item.name = t(name) + end + if value then + item.value = value + end + end + end +end + +local function replaceIngredients(table) + local ingredients = content.ingredients.records + for _, values in pairs(table) do + local id = values[1] + local ingredient = ingredients[id] + if ingredient then + for i = 1,4 do + local row = values[i + 1] + if row and implementedEffects[row.id] then + local effect = ingredient.effects[i] + effect.id = row.id + effect.affectedAttribute = row.attribute or '' + effect.affectedSkill = row.skill or '' + end + end + end + end +end + local function addSummons() local effects = content.magicEffects.records for _, values in pairs(magicData.td_summon_effects) do @@ -63,6 +164,7 @@ local function addSummons() template = 'summonbear' end effects[id] = { template = effects[template], cost = cost, icon = icon, name = t(name), description = t(desc), allowsSpellmaking = true, allowsEnchanting = true } + implementedEffects[id] = true end end @@ -71,8 +173,12 @@ return { onContentFilesLoaded = function() if version_check.isFeatureEnabled('summoningSpells') then addSummons() - replaceSpells(magicData.td_summon_spells) end + replaceSpells(magicData.td_summon_spells) + replaceEnchantments(magicData.td_enchantments) + editItems(magicData.td_enchanted_items) + replacePotions(magicData.td_potions) + replaceIngredients(magicData.td_ingredients) end } } diff --git a/Data Files/scripts/TamrielData/utils/feature_data.lua b/Data Files/scripts/TamrielData/utils/feature_data.lua index 7ee5801..0a0636d 100644 --- a/Data Files/scripts/TamrielData/utils/feature_data.lua +++ b/Data Files/scripts/TamrielData/utils/feature_data.lua @@ -19,7 +19,7 @@ features["miscSpells"] = { settingsKey = "Settings_TamrielData_page01Main_group02Magic_miscSpells" } features["summoningSpells"] = { - requiredLuaApi = 126, + requiredLuaApi = 128, settingsPlayerSectionStorageId = "Settings_TamrielData_page01Main_group02Magic", settingsEnabledByDefault = true, settingsKey = "Settings_TamrielData_page01Main_group02Magic_summoningSpells" From 5a56df1c13d824d4ca01ac8e65b8d56de1b20de7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 11 Apr 2026 19:49:57 +0200 Subject: [PATCH 09/28] Account for load order changes --- .../scripts/TamrielData/actor_magic.lua | 1 + .../scripts/TamrielData/actor_summons.lua | 8 +++--- .../scripts/TamrielData/global_magic.lua | 20 ++++++++++---- .../scripts/TamrielData/global_summons.lua | 27 +++++++++++++++---- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index bd69ee9..b405ce6 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -1,4 +1,5 @@ local core = require('openmw.core') +local self = require('openmw.self') return { engineHandlers = { diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index acfd683..9bb6a35 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -44,17 +44,17 @@ return { T_GetSummonPosition = function(data) local position = getSafeSpawn() data.position = position - data.caster = self.object core.sendGlobalEvent('T_Summon', data) end, T_MarkSummon = function(data) state.caster = data.caster - state.key = data.key + state.id = data.id + state.index = data.index self.type.stats.ai.fight(self).base = 30 -- we should probably be using dedicated creature variants end, Died = function() - if state.key ~= nil then - core.sendGlobalEvent('T_Unsummon', { creature = self.object, caster = state.caster, key = state.key }) + if state.caster ~= nil then + core.sendGlobalEvent('T_Unsummon', { creature = self.object, caster = state.caster, id = state.id, index = state.index }) end end }, diff --git a/Data Files/scripts/TamrielData/global_magic.lua b/Data Files/scripts/TamrielData/global_magic.lua index 121b998..f1640c8 100644 --- a/Data Files/scripts/TamrielData/global_magic.lua +++ b/Data Files/scripts/TamrielData/global_magic.lua @@ -33,6 +33,8 @@ local STATE_ACTIVE = 1 local STATE_ONCE = 2 local STATE_IGNORE = 3 +local MAX_WAIT = 0.25 + local appliedOnce = {} for _, effect in pairs(core.magic.effects.records) do if effect.isAppliedOnce then @@ -55,7 +57,7 @@ local function initActorState(actor, state) state.spells[spell.activeSpellId] = effects end return { - waited = math.random(), + waited = math.random() * MAX_WAIT, activeSpells = activeSpells } end @@ -75,13 +77,14 @@ local function getActorState(actor, init) end state = { delayUpdateChecks = true, - spells = {} + spells = {}, + actor = actor } persistentState.actors[id] = state tempState[id] = initActorState(actor, state) elseif not tempState[id] then tempState[id] = { - waited = math.random(), + waited = math.random() * MAX_WAIT, activeSpells = types.Actor.activeSpells(actor) } end @@ -122,6 +125,8 @@ local function updateEffects(actor, state, tempState) s = STATE_ACTIVE effects[index] = s end + elseif s == STATE_ONCE then + canDiscard = false end if s == STATE_ACTIVE then if onEffectUpdate(actor, spell, effect) then @@ -154,8 +159,6 @@ local function updateEffects(actor, state, tempState) return canDiscard end -local MAX_WAIT = 0.25 - local function waitOrUpdate(actor, dt) local state, tempState = getActorState(actor, true) if state.delayUpdateChecks then @@ -179,6 +182,13 @@ return { onLoad = function(data) if data then persistentState = data + local actors = {} + for id, actorData in pairs(data.actors) do + if actorData.actor:isValid() then + actors[actorData.actor.id] = actorData + end + end + persistentState.actors = actors end end, onUpdate = function(dt) diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index bbe39f5..78719c4 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -32,7 +32,7 @@ I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) local id = spell.activeSpellId local index = effect.index local key = toKey(caster, id, index) - state.summons[key] = { id = id, index = index, creatureId = creature } + state.summons[key] = { id = id, index = index, creatureId = creature, actor = caster } caster:sendEvent('T_GetSummonPosition', { key = key }) track.ignore = false end) @@ -56,6 +56,22 @@ I.T_ActorMagic.addEffectEndHandler(function(actor, id, index) end) return { + engineHandlers = { + onSave = function() + return state + end, + onLoad = function(data) + if data then + state = data + local summons = {} + for _, actorData in pairs(data.summons) do + local key = toKey(actorData.actor, actorData.id, actorData.index) + summons[key] = actorData + end + state.summons = summons + end + end + }, eventHandlers = { T_Summon = function(data) local effect = state.summons[data.key] @@ -63,19 +79,20 @@ return { return end local creature = world.createObject(effect.creatureId) - local caster = data.caster + local caster = effect.actor creature:teleport(caster.cell.name, data.position, { onGround = true }) creature:sendEvent('StartAIPackage', { type = 'Follow', target = caster }) - creature:sendEvent('T_MarkSummon', { key = data.key, caster = caster }) + creature:sendEvent('T_MarkSummon', { index = effect.index, id = effect.id, caster = caster }) creature:sendEvent('AddVfx', { model = startVfx }) effect.creatureId = nil effect.creature = creature end, T_Unsummon = function(data) unsummon(data.creature) - local effect = state.summons[data.key] + local key = toKey(data.caster, data.id, data.index) + local effect = state.summons[key] if effect then - state.summons[data.key] = nil + state.summons[key] = nil I.T_ActorMagic.removeEffect(data.caster, effect.id, effect.index) end end From 1fc3ed00bd59519d9b52d1ac6bb90841b6b35985 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 12 Apr 2026 10:22:26 +0200 Subject: [PATCH 10/28] Delete summons instead of disabling them --- .../scripts/TamrielData/global_summons.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index 78719c4..3f01b65 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -38,8 +38,10 @@ I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) end) local function unsummon(creature) - creature.enabled = false - world.vfx.spawn(startVfx, creature.position) + if creature:isValid() then + world.vfx.spawn(startVfx, creature.position) + creature:remove() + end end I.T_ActorMagic.addEffectEndHandler(function(actor, id, index) @@ -49,7 +51,7 @@ I.T_ActorMagic.addEffectEndHandler(function(actor, id, index) return end local creature = summon.creature - if creature and creature:isValid() then + if creature then unsummon(creature) end state.summons[key] = nil @@ -65,8 +67,12 @@ return { state = data local summons = {} for _, actorData in pairs(data.summons) do - local key = toKey(actorData.actor, actorData.id, actorData.index) - summons[key] = actorData + if actorData.actor:isValid() then + local key = toKey(actorData.actor, actorData.id, actorData.index) + summons[key] = actorData + elseif actorData.creature then + unsummon(actorData.creature) + end end state.summons = summons end From 658504a52d7999151487117f2351d34a192261bd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 14 Apr 2026 17:29:04 +0200 Subject: [PATCH 11/28] Switch to nicer syntax --- Data Files/scripts/TamrielData/load_magic.lua | 4 ++-- Data Files/scripts/TamrielData/utils/feature_data.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 0eeb72f..118206d 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -148,8 +148,8 @@ local function replaceIngredients(table) if row and implementedEffects[row.id] then local effect = ingredient.effects[i] effect.id = row.id - effect.affectedAttribute = row.attribute or '' - effect.affectedSkill = row.skill or '' + effect.affectedAttribute = row.attribute + effect.affectedSkill = row.skill end end end diff --git a/Data Files/scripts/TamrielData/utils/feature_data.lua b/Data Files/scripts/TamrielData/utils/feature_data.lua index 0a0636d..5331ef0 100644 --- a/Data Files/scripts/TamrielData/utils/feature_data.lua +++ b/Data Files/scripts/TamrielData/utils/feature_data.lua @@ -19,7 +19,7 @@ features["miscSpells"] = { settingsKey = "Settings_TamrielData_page01Main_group02Magic_miscSpells" } features["summoningSpells"] = { - requiredLuaApi = 128, + requiredLuaApi = 129, settingsPlayerSectionStorageId = "Settings_TamrielData_page01Main_group02Magic", settingsEnabledByDefault = true, settingsKey = "Settings_TamrielData_page01Main_group02Magic_summoningSpells" From f63aa311ef4842e42ff397291e51d0404fce5c45 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 15 Apr 2026 19:28:19 +0200 Subject: [PATCH 12/28] Fix typo --- Data Files/scripts/TamrielData/load_magic.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 118206d..66872d9 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -159,7 +159,7 @@ end local function addSummons() local effects = content.magicEffects.records for _, values in pairs(magicData.td_summon_effects) do - local id, name, creature, cost, icon, description, template = unpack(values) + local id, name, creature, cost, icon, desc, template = unpack(values) if template == 'callBear' then template = 'summonbear' end From 18f79bd179bb015c66464a21e0f580914b097451 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 22 Apr 2026 19:32:40 +0200 Subject: [PATCH 13/28] Reduce code duplication in setting up MWSE misc effects --- Data Files/MWSE/mods/TamrielData/magic.lua | 758 ++---------------- .../MWSE/mods/TamrielData/magicdata.lua | 52 +- 2 files changed, 110 insertions(+), 700 deletions(-) diff --git a/Data Files/MWSE/mods/TamrielData/magic.lua b/Data Files/MWSE/mods/TamrielData/magic.lua index f986f2f..06fa094 100644 --- a/Data Files/MWSE/mods/TamrielData/magic.lua +++ b/Data Files/MWSE/mods/TamrielData/magic.lua @@ -3238,42 +3238,55 @@ event.register(tes3.event.magicEffectsResolved, function() if config.miscSpells then local passwallBaseEffect = tes3.getMagicEffect(tes3.effect.detectAnimal) - local passwallSchool = tes3.magicSchool.mysticism if passwallAlteration then passwallBaseEffect = tes3.getMagicEffect(tes3.effect.levitate) - passwallSchool = tes3.magicSchool.alteration end - local soultrapEffect = tes3.getMagicEffect(tes3.effect.soultrap) - local reflectEffect = tes3.getMagicEffect(tes3.effect.reflect) - local detectEffect = tes3.getMagicEffect(tes3.effect.detectAnimal) local shieldEffect = tes3.getMagicEffect(tes3.effect.shield) - local burdenEffect = tes3.getMagicEffect(tes3.effect.burden) - local restoreEffect = tes3.getMagicEffect(tes3.effect.fortifyHealth) -- The fortify VFX feels more appropriate for the resartus effects, but perhaps it should still be restoration? - local summonDremoraEffect = tes3.getMagicEffect(tes3.effect.summonDremora) - local blindEffect = tes3.getMagicEffect(tes3.effect.blind) - local damageHealthEffect = tes3.getMagicEffect(tes3.effect.damageHealth) - local fortifyAttackEffect = tes3.getMagicEffect(tes3.effect.fortifyAttack) - local lightEffect = tes3.getMagicEffect(tes3.effect.light) - - local effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[1]) -- Passwall - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), + + local function addMiscEffect(effectID, params, templateOverride) + local effectName, effectCost, iconPath, effectDescription, templateId = unpack(magicData.td_misc_effects[effectID]) + params.id = tes3.effect[effectID] + params.name = common.i18n("magic." .. effectName) + params.description = common.i18n("magic." .. effectDescription) + params.baseCost = effectCost + if not params.icon then + params.icon = iconPath + end + local template = templateOverride or tes3.getMagicEffect(tes3.effect[templateId]) + if template then + for _, key in pairs({ "school", "speed", "casterLinked", "usesNegativeLighting", "particleTexture", + "size", "sizeCap", "hasContinuousVFX", "illegalDaedra", "targetsAttributes", "targetsSkills", + "allowEnchanting", "allowSpellmaking", "appliesOnce", "canCastSelf", "canCastTarget", "canCastTouch", + "hasNoDuration", "hasNoMagnitude", "isHarmful", "nonRecastable", "unreflectable" }) do + if params[key] == nil then + params[key] = template[key] + end + end + if not params.lighting then + params.lighting = {x = template.lightingRed / 255, y = template.lightingGreen / 255, z = template.lightingBlue / 255} + end + for key1, key2 in pairs({ boltSound = "boltSoundEffect", boltVFX = "boltVisualEffect", hitSound = "hitSoundEffect", + hitVFX = "hitVisualEffect", areaSound = "areaSoundEffect", areaVFX = "areaVisualEffect", castSound = "castSoundEffect", + castVFX = "castVisualEffect" }) do + if params[key1] == nil then + params[key1] = template[key2].id + end + end + end + tes3.addMagicEffect(params) + end + + addMiscEffect("T_mysticism_Passwall", { --magnitudeType = " " .. tes3.findGMST(tes3.gmst.sfeet).value, -- Passwall is currently set up to not have a magnitude and works off of the effect's area instead --magnitudeTypePlural = " " .. tes3.findGMST(tes3.gmst.sfeet).value, - school = passwallSchool, - baseCost = effectCost, - speed = passwallBaseEffect.speed, allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = false, canCastTarget = false, canCastTouch = true, - casterLinked = passwallBaseEffect.casterLinked, hasContinuousVFX = false, hasNoDuration = true, hasNoMagnitude = true, @@ -3283,265 +3296,107 @@ event.register(tes3.event.magicEffectsResolved, function() targetsAttributes = false, targetsSkills = false, unreflectable = true, - usesNegativeLighting = passwallBaseEffect.usesNegativeLighting, icon = passwallIcon, - particleTexture = passwallBaseEffect.particleTexture, - castSound = passwallBaseEffect.castSoundEffect.id, - castVFX = passwallBaseEffect.castVisualEffect.id, boltSound = "T_SndObj_Silence", boltVFX = "T_VFX_Empty", hitSound = "T_SndObj_Silence", hitVFX = "T_VFX_Empty", -- Currently has to use VFX because otherwise Morrowind crashes when casting the effect on some actors despite this parameter being "optional" areaSound = "T_SndObj_Silence", areaVFX = "T_VFX_Empty", -- Problems can apparently still arise from missing boltVFX and areaVFX for some people - lighting = {x = passwallBaseEffect.lightingRed / 255, y = passwallBaseEffect.lightingGreen / 255, z = passwallBaseEffect.lightingBlue / 255}, - size = passwallBaseEffect.size, - sizeCap = passwallBaseEffect.sizeCap, onTick = function(eventData) eventData:trigger() end, onCollision = nil - } + }, passwallBaseEffect) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[2]) -- Banish Daedra - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = soultrapEffect.speed, + addMiscEffect("T_mysticism_BanishDae", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = false, canCastTarget = true, canCastTouch = true, - casterLinked = soultrapEffect.casterLinked, - hasContinuousVFX = soultrapEffect.hasContinuousVFX, hasNoDuration = true, hasNoMagnitude = false, - illegalDaedra = soultrapEffect.illegalDaedra, isHarmful = false, nonRecastable = true, - targetsAttributes = soultrapEffect.targetsAttributes, - targetsSkills = soultrapEffect.targetsSkills, unreflectable = true, - usesNegativeLighting = soultrapEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = soultrapEffect.particleTexture, - castSound = soultrapEffect.castSoundEffect.id, - castVFX = soultrapEffect.castVisualEffect.id, - boltSound = soultrapEffect.boltSoundEffect.id, - boltVFX = soultrapEffect.boltVisualEffect.id, hitSound = "T_SndObj_Silence", hitVFX = "T_VFX_Empty", areaSound = "T_SndObj_Silence", areaVFX = "T_VFX_Empty", - lighting = {x = soultrapEffect.lightingRed / 255, y = soultrapEffect.lightingGreen / 255, z = soultrapEffect.lightingBlue / 255}, - size = soultrapEffect.size, - sizeCap = soultrapEffect.sizeCap, onTick = banishDaedraEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[3]) -- Reflect Damage - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), + addMiscEffect("T_mysticism_ReflectDmg", { magnitudeType = tes3.findGMST(tes3.gmst.spercent).value, magnitudeTypePlural = tes3.findGMST(tes3.gmst.spercent).value, - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = reflectEffect.speed, - allowEnchanting = reflectEffect.allowEnchanting, - allowSpellmaking = reflectEffect.allowSpellmaking, - appliesOnce = reflectEffect.appliesOnce, - canCastSelf = reflectEffect.canCastSelf, - canCastTarget = reflectEffect.canCastTarget, - canCastTouch = reflectEffect.canCastTouch, - casterLinked = reflectEffect.casterLinked, - hasContinuousVFX = reflectEffect.hasContinuousVFX, - hasNoDuration = reflectEffect.hasNoDuration, - hasNoMagnitude = reflectEffect.hasNoMagnitude, - illegalDaedra = reflectEffect.illegalDaedra, - isHarmful = reflectEffect.isHarmful, - nonRecastable = reflectEffect.nonRecastable, - targetsAttributes = reflectEffect.targetsAttributes, - targetsSkills = reflectEffect.targetsSkills, - unreflectable = reflectEffect.unreflectable, - usesNegativeLighting = reflectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = reflectEffect.particleTexture, - castSound = reflectEffect.castSoundEffect.id, - castVFX = reflectEffect.castVisualEffect.id, - boltSound = reflectEffect.boltSoundEffect.id, - boltVFX = reflectEffect.boltVisualEffect.id, - hitSound = reflectEffect.hitSoundEffect.id, - hitVFX = reflectEffect.hitVisualEffect.id, - areaSound = reflectEffect.areaSoundEffect.id, - areaVFX = reflectEffect.areaVisualEffect.id, - lighting = {x = reflectEffect.lightingRed / 255, y = reflectEffect.lightingGreen / 255, z = reflectEffect.lightingBlue / 255}, - size = reflectEffect.size, - sizeCap = reflectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[4]) -- Detect Humanoid - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), + addMiscEffect("T_mysticism_DetHuman", { magnitudeType = " " .. tes3.findGMST(tes3.gmst.sfeet).value, magnitudeTypePlural = " " .. tes3.findGMST(tes3.gmst.sfeet).value, - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = detectEffect.speed, allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = detectEffect.casterLinked, - hasContinuousVFX = detectEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = detectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = detectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = detectEffect.particleTexture, - castSound = detectEffect.castSoundEffect.id, - castVFX = detectEffect.castVisualEffect.id, - boltSound = detectEffect.boltSoundEffect.id, - boltVFX = detectEffect.boltVisualEffect.id, - hitSound = detectEffect.hitSoundEffect.id, - hitVFX = detectEffect.hitVisualEffect.id, - areaSound = detectEffect.areaSoundEffect.id, - areaVFX = detectEffect.areaVisualEffect.id, - lighting = {x = detectEffect.lightingRed / 255, y = detectEffect.lightingGreen / 255, z = detectEffect.lightingBlue / 255}, - size = detectEffect.size, - sizeCap = detectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[5]) -- Radiant Shield - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.alteration, - baseCost = effectCost, - speed = shieldEffect.speed, - allowEnchanting = shieldEffect.allowEnchanting, - allowSpellmaking = shieldEffect.allowSpellmaking, - appliesOnce = shieldEffect.appliesOnce, - canCastSelf = shieldEffect.canCastSelf, - canCastTarget = shieldEffect.canCastTarget, - canCastTouch = shieldEffect.canCastTouch, - casterLinked = shieldEffect.casterLinked, - hasContinuousVFX = shieldEffect.hasContinuousVFX, - hasNoDuration = shieldEffect.hasNoDuration, - hasNoMagnitude = shieldEffect.hasNoMagnitude, - illegalDaedra = shieldEffect.illegalDaedra, - isHarmful = shieldEffect.isHarmful, - nonRecastable = shieldEffect.nonRecastable, - targetsAttributes = shieldEffect.targetsAttributes, - targetsSkills = shieldEffect.targetsSkills, - unreflectable = shieldEffect.unreflectable, - usesNegativeLighting = shieldEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = shieldEffect.particleTexture, - castSound = shieldEffect.castSoundEffect.id, - castVFX = shieldEffect.castVisualEffect.id, - boltSound = shieldEffect.boltSoundEffect.id, - boltVFX = shieldEffect.boltVisualEffect.id, - hitSound = shieldEffect.hitSoundEffect.id, + addMiscEffect("T_alteration_RadShield", { hitVFX = "T_VFX_RadiantShieldHit", - areaSound = shieldEffect.areaSoundEffect.id, - areaVFX = shieldEffect.areaVisualEffect.id, lighting = {x = 128, y = 128, z = 128}, - size = shieldEffect.size, - sizeCap = shieldEffect.sizeCap, onTick = radiantShieldEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[6]) -- Wabbajack - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.alteration, - baseCost = effectCost, - speed = burdenEffect.speed, + addMiscEffect("T_alteration_Wabbajack", { allowEnchanting = false, allowSpellmaking = false, appliesOnce = true, canCastSelf = false, canCastTarget = true, canCastTouch = false, - casterLinked = burdenEffect.casterLinked, - hasContinuousVFX = burdenEffect.hasContinuousVFX, hasNoDuration = true, hasNoMagnitude = true, - illegalDaedra = burdenEffect.illegalDaedra, isHarmful = true, nonRecastable = true, targetsAttributes = false, targetsSkills = false, unreflectable = true, - usesNegativeLighting = burdenEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = burdenEffect.particleTexture, - castSound = burdenEffect.castSoundEffect.id, - castVFX = burdenEffect.castVisualEffect.id, - boltSound = burdenEffect.boltSoundEffect.id, - boltVFX = burdenEffect.boltVisualEffect.id, hitSound = "T_SndObj_Silence", hitVFX = "T_VFX_Empty", areaSound = "T_SndObj_Silence", areaVFX = "T_VFX_Empty", - lighting = {x = burdenEffect.lightingRed / 255, y = burdenEffect.lightingGreen / 255, z = burdenEffect.lightingBlue / 255}, - size = burdenEffect.size, - sizeCap = burdenEffect.sizeCap, onTick = wabbajackEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[7]) -- Wabbajack Helper - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.alteration, - baseCost = effectCost, - speed = burdenEffect.speed, + addMiscEffect("T_alteration_WabbajackHelper", { allowEnchanting = false, allowSpellmaking = false, appliesOnce = true, canCastSelf = false, canCastTarget = true, canCastTouch = true, - casterLinked = burdenEffect.casterLinked, - hasContinuousVFX = burdenEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = true, - illegalDaedra = burdenEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = true, - usesNegativeLighting = burdenEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = burdenEffect.particleTexture, castSound = "T_SndObj_Silence", castVFX = "T_VFX_Empty", boltSound = "T_SndObj_Silence", @@ -3550,780 +3405,335 @@ event.register(tes3.event.magicEffectsResolved, function() hitVFX = "T_VFX_Empty", areaSound = "T_SndObj_Silence", areaVFX = "T_VFX_Empty", - lighting = {x = burdenEffect.lightingRed / 255, y = burdenEffect.lightingGreen / 255, z = burdenEffect.lightingBlue / 255}, - size = burdenEffect.size, - sizeCap = burdenEffect.sizeCap, onTick = wabbajackHelperEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[8]) -- Insight - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = reflectEffect.speed, + addMiscEffect("T_mysticism_Insight", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = reflectEffect.casterLinked, - hasContinuousVFX = reflectEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = reflectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = reflectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = reflectEffect.particleTexture, - castSound = reflectEffect.castSoundEffect.id, - castVFX = reflectEffect.castVisualEffect.id, - boltSound = reflectEffect.boltSoundEffect.id, - boltVFX = reflectEffect.boltVisualEffect.id, - hitSound = reflectEffect.hitSoundEffect.id, - hitVFX = reflectEffect.hitVisualEffect.id, - areaSound = reflectEffect.areaSoundEffect.id, - areaVFX = reflectEffect.areaVisualEffect.id, - lighting = {x = reflectEffect.lightingRed / 255, y = reflectEffect.lightingGreen / 255, z = reflectEffect.lightingBlue / 255}, - size = reflectEffect.size, - sizeCap = reflectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[9]) -- Armor Resartus - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.restoration, - baseCost = effectCost, - speed = restoreEffect.speed, + addMiscEffect("T_restoration_ArmorResartus", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = restoreEffect.casterLinked, - hasContinuousVFX = restoreEffect.hasContinuousVFX, hasNoDuration = true, hasNoMagnitude = false, - illegalDaedra = restoreEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = restoreEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = restoreEffect.particleTexture, - castSound = restoreEffect.castSoundEffect.id, - castVFX = restoreEffect.castVisualEffect.id, - boltSound = restoreEffect.boltSoundEffect.id, - boltVFX = restoreEffect.boltVisualEffect.id, - hitSound = restoreEffect.hitSoundEffect.id, - hitVFX = restoreEffect.hitVisualEffect.id, - areaSound = restoreEffect.areaSoundEffect.id, - areaVFX = restoreEffect.areaVisualEffect.id, - lighting = {x = restoreEffect.lightingRed / 255, y = restoreEffect.lightingGreen / 255, z = restoreEffect.lightingBlue / 255}, - size = restoreEffect.size, - sizeCap = restoreEffect.sizeCap, onTick = armorResartusEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[10]) -- Weapon Resartus - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.restoration, - baseCost = effectCost, - speed = restoreEffect.speed, + addMiscEffect("T_restoration_WeaponResartus", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = restoreEffect.casterLinked, - hasContinuousVFX = restoreEffect.hasContinuousVFX, hasNoDuration = true, hasNoMagnitude = false, - illegalDaedra = restoreEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = restoreEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = restoreEffect.particleTexture, - castSound = restoreEffect.castSoundEffect.id, - castVFX = restoreEffect.castVisualEffect.id, - boltSound = restoreEffect.boltSoundEffect.id, - boltVFX = restoreEffect.boltVisualEffect.id, - hitSound = restoreEffect.hitSoundEffect.id, - hitVFX = restoreEffect.hitVisualEffect.id, - areaSound = restoreEffect.areaSoundEffect.id, - areaVFX = restoreEffect.areaVisualEffect.id, - lighting = {x = restoreEffect.lightingRed / 255, y = restoreEffect.lightingGreen / 255, z = restoreEffect.lightingBlue / 255}, - size = restoreEffect.size, - sizeCap = restoreEffect.sizeCap, onTick = weaponResartusEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[11]) -- Corruption - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.conjuration, - baseCost = effectCost, - speed = summonDremoraEffect.speed, + addMiscEffect("T_conjuration_Corruption", { allowEnchanting = false, allowSpellmaking = false, appliesOnce = true, canCastSelf = false, canCastTarget = true, canCastTouch = false, - casterLinked = summonDremoraEffect.casterLinked, - hasContinuousVFX = summonDremoraEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = true, - illegalDaedra = summonDremoraEffect.illegalDaedra, isHarmful = true, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = true, - usesNegativeLighting = summonDremoraEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = summonDremoraEffect.particleTexture, - castSound = summonDremoraEffect.castSoundEffect.id, - castVFX = summonDremoraEffect.castVisualEffect.id, - boltSound = summonDremoraEffect.boltSoundEffect.id, - boltVFX = summonDremoraEffect.boltVisualEffect.id, - hitSound = summonDremoraEffect.hitSoundEffect.id, - hitVFX = summonDremoraEffect.hitVisualEffect.id, - areaSound = summonDremoraEffect.areaSoundEffect.id, - areaVFX = summonDremoraEffect.areaVisualEffect.id, - lighting = {x = summonDremoraEffect.lightingRed / 255, y = summonDremoraEffect.lightingGreen / 255, z = summonDremoraEffect.lightingBlue / 255}, - size = summonDremoraEffect.size, - sizeCap = summonDremoraEffect.sizeCap, onTick = corruptionEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[12]) -- Corruption Summon - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.conjuration, - baseCost = effectCost, - speed = summonDremoraEffect.speed, + addMiscEffect("T_conjuration_CorruptionSummon", { allowEnchanting = false, allowSpellmaking = false, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = summonDremoraEffect.casterLinked, - hasContinuousVFX = summonDremoraEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = true, - illegalDaedra = summonDremoraEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, - unreflectable = summonDremoraEffect.unreflectable, - usesNegativeLighting = summonDremoraEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = summonDremoraEffect.particleTexture, - castSound = summonDremoraEffect.castSoundEffect.id, - castVFX = summonDremoraEffect.castVisualEffect.id, - boltSound = summonDremoraEffect.boltSoundEffect.id, - boltVFX = summonDremoraEffect.boltVisualEffect.id, - hitSound = summonDremoraEffect.hitSoundEffect.id, - hitVFX = summonDremoraEffect.hitVisualEffect.id, - areaSound = summonDremoraEffect.areaSoundEffect.id, - areaVFX = summonDremoraEffect.areaVisualEffect.id, - lighting = {x = summonDremoraEffect.lightingRed / 255, y = summonDremoraEffect.lightingGreen / 255, z = summonDremoraEffect.lightingBlue / 255}, - size = summonDremoraEffect.size, - sizeCap = summonDremoraEffect.sizeCap, onTick = function(eventData) eventData:triggerSummon(corruptionActorID) end, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[13]) -- Distract Creature - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.illusion, - baseCost = effectCost, - speed = blindEffect.speed, + addMiscEffect("T_illusion_DistractCreature", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = false, canCastTarget = true, -- The GUI for making custom magic effects doesn't like just having an effect only work at target range, so the distract spells also work at touch range for now canCastTouch = true, - casterLinked = blindEffect.casterLinked, - hasContinuousVFX = blindEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = blindEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = true, - usesNegativeLighting = blindEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = blindEffect.particleTexture, - castSound = blindEffect.castSoundEffect.id, - castVFX = blindEffect.castVisualEffect.id, - boltSound = blindEffect.boltSoundEffect.id, - boltVFX = blindEffect.boltVisualEffect.id, - hitSound = blindEffect.hitSoundEffect.id, - hitVFX = blindEffect.hitVisualEffect.id, - areaSound = blindEffect.areaSoundEffect.id, - areaVFX = blindEffect.areaVisualEffect.id, - lighting = {x = blindEffect.lightingRed / 255, y = blindEffect.lightingGreen / 255, z = blindEffect.lightingBlue / 255}, - size = blindEffect.size, - sizeCap = blindEffect.sizeCap, onTick = distractCreatureEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[14]) -- Distract Humanoid - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.illusion, - baseCost = effectCost, - speed = blindEffect.speed, + addMiscEffect("T_illusion_DistractHumanoid", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = false, canCastTarget = true, canCastTouch = true, - casterLinked = blindEffect.casterLinked, - hasContinuousVFX = blindEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = blindEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = true, - usesNegativeLighting = blindEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = blindEffect.particleTexture, - castSound = blindEffect.castSoundEffect.id, - castVFX = blindEffect.castVisualEffect.id, - boltSound = blindEffect.boltSoundEffect.id, - boltVFX = blindEffect.boltVisualEffect.id, - hitSound = blindEffect.hitSoundEffect.id, - hitVFX = blindEffect.hitVisualEffect.id, - areaSound = blindEffect.areaSoundEffect.id, - areaVFX = blindEffect.areaVisualEffect.id, - lighting = {x = blindEffect.lightingRed / 255, y = blindEffect.lightingGreen / 255, z = blindEffect.lightingBlue / 255}, - size = blindEffect.size, - sizeCap = blindEffect.sizeCap, onTick = distractHumanoidEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[15]) -- Gaze of Veloth - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.destruction, - baseCost = effectCost, - speed = damageHealthEffect.speed, + addMiscEffect("T_destruction_GazeOfVeloth", { allowEnchanting = false, allowSpellmaking = false, appliesOnce = true, canCastSelf = false, canCastTarget = true, canCastTouch = true, - casterLinked = damageHealthEffect.casterLinked, - hasContinuousVFX = damageHealthEffect.hasContinuousVFX, hasNoDuration = true, hasNoMagnitude = true, - illegalDaedra = damageHealthEffect.illegalDaedra, isHarmful = true, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = true, - usesNegativeLighting = damageHealthEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = damageHealthEffect.particleTexture, - castSound = damageHealthEffect.castSoundEffect.id, - castVFX = damageHealthEffect.castVisualEffect.id, - boltSound = damageHealthEffect.boltSoundEffect.id, - boltVFX = damageHealthEffect.boltVisualEffect.id, - hitSound = damageHealthEffect.hitSoundEffect.id, - hitVFX = damageHealthEffect.hitVisualEffect.id, - areaSound = damageHealthEffect.areaSoundEffect.id, - areaVFX = damageHealthEffect.areaVisualEffect.id, - lighting = {x = damageHealthEffect.lightingRed / 255, y = damageHealthEffect.lightingGreen / 255, z = damageHealthEffect.lightingBlue / 255}, - size = damageHealthEffect.size, - sizeCap = damageHealthEffect.sizeCap, onTick = gazeOfVelothEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[16]) -- Detect Enemy - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), + addMiscEffect("T_mysticism_DetEnemy", { magnitudeType = " " .. tes3.findGMST(tes3.gmst.sfeet).value, magnitudeTypePlural = " " .. tes3.findGMST(tes3.gmst.sfeet).value, - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = detectEffect.speed, allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = detectEffect.casterLinked, - hasContinuousVFX = detectEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = detectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = detectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = detectEffect.particleTexture, - castSound = detectEffect.castSoundEffect.id, - castVFX = detectEffect.castVisualEffect.id, - boltSound = detectEffect.boltSoundEffect.id, - boltVFX = detectEffect.boltVisualEffect.id, - hitSound = detectEffect.hitSoundEffect.id, - hitVFX = detectEffect.hitVisualEffect.id, - areaSound = detectEffect.areaSoundEffect.id, - areaVFX = detectEffect.areaVisualEffect.id, - lighting = {x = detectEffect.lightingRed / 255, y = detectEffect.lightingGreen / 255, z = detectEffect.lightingBlue / 255}, - size = detectEffect.size, - sizeCap = detectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[17]) -- Detect Invisibility - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), + addMiscEffect("T_mysticism_DetInvisibility", { magnitudeType = " " .. tes3.findGMST(tes3.gmst.sfeet).value, magnitudeTypePlural = " " .. tes3.findGMST(tes3.gmst.sfeet).value, - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = detectEffect.speed, allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = detectEffect.casterLinked, - hasContinuousVFX = detectEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = detectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = detectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = detectEffect.particleTexture, - castSound = detectEffect.castSoundEffect.id, - castVFX = detectEffect.castVisualEffect.id, - boltSound = detectEffect.boltSoundEffect.id, - boltVFX = detectEffect.boltVisualEffect.id, - hitSound = detectEffect.hitSoundEffect.id, - hitVFX = detectEffect.hitVisualEffect.id, - areaSound = detectEffect.areaSoundEffect.id, - areaVFX = detectEffect.areaVisualEffect.id, - lighting = {x = detectEffect.lightingRed / 255, y = detectEffect.lightingGreen / 255, z = detectEffect.lightingBlue / 255}, - size = detectEffect.size, - sizeCap = detectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[18]) -- Blink - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), + addMiscEffect("T_mysticism_Blink", { magnitudeType = " " .. tes3.findGMST(tes3.gmst.sfeet).value, magnitudeTypePlural = " " .. tes3.findGMST(tes3.gmst.sfeet).value, - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = detectEffect.speed, allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = detectEffect.casterLinked, - hasContinuousVFX = detectEffect.hasContinuousVFX, hasNoDuration = true, hasNoMagnitude = false, - illegalDaedra = detectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = detectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = detectEffect.particleTexture, - castSound = detectEffect.castSoundEffect.id, - castVFX = detectEffect.castVisualEffect.id, - boltSound = detectEffect.boltSoundEffect.id, - boltVFX = detectEffect.boltVisualEffect.id, hitSound = "T_SndObj_BlinkHit", hitVFX = "T_VFX_Empty", - areaSound = detectEffect.areaSoundEffect.id, - areaVFX = detectEffect.areaVisualEffect.id, - lighting = {x = detectEffect.lightingRed / 255, y = detectEffect.lightingGreen / 255, z = detectEffect.lightingBlue / 255}, - size = detectEffect.size, - sizeCap = detectEffect.sizeCap, onTick = blinkEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[19]) -- Fortify Casting - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.restoration, - baseCost = effectCost, - speed = fortifyAttackEffect.speed, + addMiscEffect("T_restoration_FortifyCasting", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = fortifyAttackEffect.casterLinked, - hasContinuousVFX = fortifyAttackEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = fortifyAttackEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = fortifyAttackEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = fortifyAttackEffect.particleTexture, - castSound = fortifyAttackEffect.castSoundEffect.id, - castVFX = fortifyAttackEffect.castVisualEffect.id, - boltSound = fortifyAttackEffect.boltSoundEffect.id, - boltVFX = fortifyAttackEffect.boltVisualEffect.id, - hitSound = fortifyAttackEffect.hitSoundEffect.id, - hitVFX = fortifyAttackEffect.hitVisualEffect.id, - areaSound = fortifyAttackEffect.areaSoundEffect.id, - areaVFX = fortifyAttackEffect.areaVisualEffect.id, - lighting = {x = fortifyAttackEffect.lightingRed / 255, y = fortifyAttackEffect.lightingGreen / 255, z = fortifyAttackEffect.lightingBlue / 255}, - size = fortifyAttackEffect.size, - sizeCap = fortifyAttackEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[20]) -- Prismatic Light - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.illusion, - baseCost = effectCost, - speed = lightEffect.speed, - allowEnchanting = lightEffect.allowEnchanting, - allowSpellmaking = lightEffect.allowSpellmaking, - appliesOnce = lightEffect.appliesOnce, - canCastSelf = lightEffect.canCastSelf, - canCastTarget = lightEffect.canCastTarget, - canCastTouch = lightEffect.canCastTouch, - casterLinked = lightEffect.casterLinked, - hasContinuousVFX = lightEffect.hasContinuousVFX, - hasNoDuration = lightEffect.hasNoDuration, - hasNoMagnitude = lightEffect.hasNoMagnitude, - illegalDaedra = lightEffect.illegalDaedra, - isHarmful = lightEffect.isHarmful, - nonRecastable = lightEffect.nonRecastable, - targetsAttributes = lightEffect.targetsAttributes, - targetsSkills = lightEffect.targetsSkills, - unreflectable = lightEffect.unreflectable, - usesNegativeLighting = lightEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = lightEffect.particleTexture, - castSound = lightEffect.castSoundEffect.id, - castVFX = lightEffect.castVisualEffect.id, - boltSound = lightEffect.boltSoundEffect.id, - boltVFX = lightEffect.boltVisualEffect.id, - hitSound = lightEffect.hitSoundEffect.id, - hitVFX = lightEffect.hitVisualEffect.id, - areaSound = lightEffect.areaSoundEffect.id, - areaVFX = lightEffect.areaVisualEffect.id, - lighting = {x = lightEffect.lightingRed / 255, y = lightEffect.lightingGreen / 255, z = lightEffect.lightingBlue / 255}, - size = lightEffect.size, - sizeCap = lightEffect.sizeCap, + addMiscEffect("T_illusion_PrismaticLight", { onTick = prismaticLightEffect, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[21]) -- Blood Magic - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = detectEffect.speed, + addMiscEffect("T_mysticism_BloodMagic", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = false, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = detectEffect.casterLinked, - hasContinuousVFX = detectEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = true, - illegalDaedra = detectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = detectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = detectEffect.particleTexture, - castSound = detectEffect.castSoundEffect.id, - castVFX = detectEffect.castVisualEffect.id, - boltSound = detectEffect.boltSoundEffect.id, - boltVFX = detectEffect.boltVisualEffect.id, - hitSound = detectEffect.hitSoundEffect.id, - hitVFX = detectEffect.hitVisualEffect.id, - areaSound = detectEffect.areaSoundEffect.id, - areaVFX = detectEffect.areaVisualEffect.id, - lighting = {x = detectEffect.lightingRed / 255, y = detectEffect.lightingGreen / 255, z = detectEffect.lightingBlue / 255}, - size = detectEffect.size, - sizeCap = detectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[22]) -- Sanguine Rose - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.conjuration, - baseCost = effectCost, - speed = summonDremoraEffect.speed, + addMiscEffect("T_conjuration_SanguineRose", { allowEnchanting = false, allowSpellmaking = false, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = summonDremoraEffect.casterLinked, - hasContinuousVFX = summonDremoraEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = true, - illegalDaedra = summonDremoraEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, - unreflectable = summonDremoraEffect.unreflectable, - usesNegativeLighting = summonDremoraEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = summonDremoraEffect.particleTexture, - castSound = summonDremoraEffect.castSoundEffect.id, - castVFX = summonDremoraEffect.castVisualEffect.id, - boltSound = summonDremoraEffect.boltSoundEffect.id, - boltVFX = summonDremoraEffect.boltVisualEffect.id, - hitSound = summonDremoraEffect.hitSoundEffect.id, - hitVFX = summonDremoraEffect.hitVisualEffect.id, - areaSound = summonDremoraEffect.areaSoundEffect.id, - areaVFX = summonDremoraEffect.areaVisualEffect.id, - lighting = {x = summonDremoraEffect.lightingRed / 255, y = summonDremoraEffect.lightingGreen / 255, z = summonDremoraEffect.lightingBlue / 255}, - size = summonDremoraEffect.size, - sizeCap = summonDremoraEffect.sizeCap, onTick = function(eventData) eventData:triggerSummon(sanguineRoseDaedra[math.random(#sanguineRoseDaedra)]) end, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[23]) -- Detect Valuable - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), + addMiscEffect("T_mysticism_DetValuables", { magnitudeType = " " .. tes3.findGMST(tes3.gmst.sfeet).value, magnitudeTypePlural = " " .. tes3.findGMST(tes3.gmst.sfeet).value, - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = detectEffect.speed, allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = detectEffect.casterLinked, - hasContinuousVFX = detectEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = false, - illegalDaedra = detectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = detectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = detectEffect.particleTexture, - castSound = detectEffect.castSoundEffect.id, - castVFX = detectEffect.castVisualEffect.id, - boltSound = detectEffect.boltSoundEffect.id, - boltVFX = detectEffect.boltVisualEffect.id, - hitSound = detectEffect.hitSoundEffect.id, - hitVFX = detectEffect.hitVisualEffect.id, - areaSound = detectEffect.areaSoundEffect.id, - areaVFX = detectEffect.areaVisualEffect.id, - lighting = {x = detectEffect.lightingRed / 255, y = detectEffect.lightingGreen / 255, z = detectEffect.lightingBlue / 255}, - size = detectEffect.size, - sizeCap = detectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[24]) -- Magicka Ward - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.mysticism, - baseCost = effectCost, - speed = detectEffect.speed, + addMiscEffect("T_mysticism_MagickaWard", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = false, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = detectEffect.casterLinked, hasContinuousVFX = shieldEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = true, - illegalDaedra = detectEffect.illegalDaedra, isHarmful = false, nonRecastable = false, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = detectEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = detectEffect.particleTexture, - castSound = detectEffect.castSoundEffect.id, - castVFX = detectEffect.castVisualEffect.id, - boltSound = detectEffect.boltSoundEffect.id, - boltVFX = detectEffect.boltVisualEffect.id, - hitSound = detectEffect.hitSoundEffect.id, hitVFX = shieldEffect.hitVisualEffect.id, - areaSound = detectEffect.areaSoundEffect.id, - areaVFX = detectEffect.areaVisualEffect.id, lighting = {x = shieldEffect.lightingRed / 255, y = shieldEffect.lightingGreen / 255, z = shieldEffect.lightingBlue / 255}, - size = detectEffect.size, - sizeCap = detectEffect.sizeCap, onTick = nil, onCollision = nil - } + }) - effectID, effectName, effectCost, iconPath, effectDescription = unpack(magicData.td_misc_effects[25]) -- Ethereal - tes3.addMagicEffect{ - id = tes3.effect[effectID], - name = common.i18n("magic." .. effectName), - description = common.i18n("magic." .. effectDescription), - school = tes3.magicSchool.illusion, - baseCost = effectCost, - speed = blindEffect.speed, + addMiscEffect("T_illusion_Ethereal", { allowEnchanting = true, allowSpellmaking = true, appliesOnce = true, canCastSelf = true, canCastTarget = false, canCastTouch = false, - casterLinked = blindEffect.casterLinked, - hasContinuousVFX = blindEffect.hasContinuousVFX, hasNoDuration = false, hasNoMagnitude = true, - illegalDaedra = blindEffect.illegalDaedra, isHarmful = false, nonRecastable = true, targetsAttributes = false, targetsSkills = false, unreflectable = false, - usesNegativeLighting = blindEffect.usesNegativeLighting, - icon = iconPath, - particleTexture = blindEffect.particleTexture, - castSound = blindEffect.castSoundEffect.id, - castVFX = blindEffect.castVisualEffect.id, - boltSound = blindEffect.boltSoundEffect.id, - boltVFX = blindEffect.boltVisualEffect.id, - hitSound = blindEffect.hitSoundEffect.id, - hitVFX = blindEffect.hitVisualEffect.id, - areaSound = blindEffect.areaSoundEffect.id, - areaVFX = blindEffect.areaVisualEffect.id, - lighting = {x = blindEffect.lightingRed / 255, y = blindEffect.lightingGreen / 255, z = blindEffect.lightingBlue / 255}, - size = blindEffect.size, - sizeCap = blindEffect.sizeCap, onTick = etherealEffect, onCollision = nil - } + }) end end) diff --git a/Data Files/MWSE/mods/TamrielData/magicdata.lua b/Data Files/MWSE/mods/TamrielData/magicdata.lua index 393c717..ab45908 100644 --- a/Data Files/MWSE/mods/TamrielData/magicdata.lua +++ b/Data Files/MWSE/mods/TamrielData/magicdata.lua @@ -52,33 +52,33 @@ local td_intervention_effects = { { "T_intervention_Kyne", "interventionKyne", 150, "td\\s\\td_s_int_kyne.tga", "interventionKyneDesc"}, } --- effect id, effect name, effect mana cost, icon, effect description +-- effect id => effect name, effect mana cost, icon, effect description, template local td_misc_effects = { - { "T_mysticism_Passwall", "miscPasswall", 750, "td\\s\\td_s_passwall.tga", "miscPasswallDesc"}, - { "T_mysticism_BanishDae", "miscBanish", 128, "td\\s\\td_s_ban_daedra.tga", "miscBanishDesc"}, - { "T_mysticism_ReflectDmg", "miscReflectDamage", 20, "td\\s\\td_s_ref_dam.tga", "miscReflectDamageDesc"}, - { "T_mysticism_DetHuman", "miscDetectHumanoid", .75, "td\\s\\td_s_det_hum.tga", "miscDetectHumanoidDesc"}, - { "T_alteration_RadShield", "miscRadiantShield", 5, "td\\s\\td_s_radiant_shield.tga", "miscRadiantShieldDesc"}, - { "T_alteration_Wabbajack", "miscWabbajack", 22, "td\\s\\td_s_wabbajack.tga", "miscWabbajackDesc"}, - { "T_alteration_WabbajackHelper", "miscWabbajack", 0, "td\\s\\td_s_wabbajack.tga", "miscWabbajackDesc"}, - { "T_mysticism_Insight", "miscInsight", 10, "td\\s\\td_s_insight.tga", "miscInsightDesc"}, - { "T_restoration_ArmorResartus", "miscArmorResartus", 60, "td\\s\\td_s_restore_ar.tga", "miscArmorResartusDesc"}, - { "T_restoration_WeaponResartus", "miscWeaponResartus", 120, "td\\s\\td_s_restore_wpn.tga", "miscWeaponResartusDesc"}, - { "T_conjuration_Corruption", "miscCorruption", 40, "td\\s\\td_s_skull_corr.tga", "miscCorruptionDesc"}, - { "T_conjuration_CorruptionSummon", "miscCorruption", 0, "td\\s\\td_s_skull_corr.tga", "miscCorruptionDesc"}, - { "T_illusion_DistractCreature", "miscDistractCreature", 0.5, "td\\s\\td_s_dist_cre.tga", "miscDistractCreatureDesc"}, - { "T_illusion_DistractHumanoid", "miscDistractHumanoid", 1, "td\\s\\td_s_dist_hum.tga", "miscDistractHumanoidDesc"}, - { "T_destruction_GazeOfVeloth", "miscGazeOfVeloth", 80, "td\\s\\td_s_gaze_veloth.tga", "miscGazeOfVelothDesc"}, - { "T_mysticism_DetEnemy", "miscDetectEnemy", 1, "td\\s\\td_s_det_enemy.tga", "miscDetectEnemyDesc"}, - { "T_mysticism_DetInvisibility", "miscDetectInvisibility", 1.5, "td\\s\\td_s_det_invisibility.tga", "miscDetectInvisibilityDesc"}, - { "T_mysticism_Blink", "miscBlink", 10, "td\\s\\td_s_blink.tga", "miscBlinkDesc"}, - { "T_restoration_FortifyCasting", "miscFortifyCasting", 1, "td\\s\\td_s_ftfy_cast.tga", "miscFortifyCastingDesc"}, - { "T_illusion_PrismaticLight", "miscPrismaticLight", 0.4, "td\\s\\td_s_p_light.tga", "miscPrismaticLightDesc"}, - { "T_mysticism_BloodMagic", "miscBloodMagic", 2, "td\\s\\td_s_blood_magic.tga", "miscBloodMagicDesc"}, - { "T_conjuration_SanguineRose", "miscSanguineRose", 40, "td\\s\\td_s_sanguine.dds.tga", "miscSanguineRoseDesc"}, - { "T_mysticism_DetValuables", "miscDetectValuables", 1.5, "td\\s\\td_s_det_value.tga", "miscDetectValuablesDesc"}, - { "T_mysticism_MagickaWard", "miscMagickaWard", 20, "td\\s\\td_s_magickaward.tga", "miscMagickaWardDesc"}, - { "T_illusion_Ethereal", "miscEthereal", 40, "td\\s\\td_s_ethereal.tga", "miscEtherealtDesc"}, + T_mysticism_Passwall = { "miscPasswall", 750, "td\\s\\td_s_passwall.tga", "miscPasswallDesc", "detectAnimal" }, + T_mysticism_BanishDae = { "miscBanish", 128, "td\\s\\td_s_ban_daedra.tga", "miscBanishDesc", "soultrap" }, + T_mysticism_ReflectDmg = { "miscReflectDamage", 20, "td\\s\\td_s_ref_dam.tga", "miscReflectDamageDesc", "reflect" }, + T_mysticism_DetHuman = { "miscDetectHumanoid", .75, "td\\s\\td_s_det_hum.tga", "miscDetectHumanoidDesc", "detectAnimal" }, + T_alteration_RadShield = { "miscRadiantShield", 5, "td\\s\\td_s_radiant_shield.tga", "miscRadiantShieldDesc", "shield" }, + T_alteration_Wabbajack = { "miscWabbajack", 22, "td\\s\\td_s_wabbajack.tga", "miscWabbajackDesc", "burden" }, + T_alteration_WabbajackHelper = { "miscWabbajack", 0, "td\\s\\td_s_wabbajack.tga", "miscWabbajackDesc", "burden" }, + T_mysticism_Insight = { "miscInsight", 10, "td\\s\\td_s_insight.tga", "miscInsightDesc", "reflect" }, + T_restoration_ArmorResartus = { "miscArmorResartus", 60, "td\\s\\td_s_restore_ar.tga", "miscArmorResartusDesc", "fortifyHealth" }, + T_restoration_WeaponResartus = { "miscWeaponResartus", 120, "td\\s\\td_s_restore_wpn.tga", "miscWeaponResartusDesc", "fortifyHealth" }, + T_conjuration_Corruption = { "miscCorruption", 40, "td\\s\\td_s_skull_corr.tga", "miscCorruptionDesc", "summonDremora" }, + T_conjuration_CorruptionSummon = { "miscCorruption", 0, "td\\s\\td_s_skull_corr.tga", "miscCorruptionDesc", "summonDremora" }, + T_illusion_DistractCreature = { "miscDistractCreature", 0.5, "td\\s\\td_s_dist_cre.tga", "miscDistractCreatureDesc", "blind" }, + T_illusion_DistractHumanoid = { "miscDistractHumanoid", 1, "td\\s\\td_s_dist_hum.tga", "miscDistractHumanoidDesc", "blind" }, + T_destruction_GazeOfVeloth = { "miscGazeOfVeloth", 80, "td\\s\\td_s_gaze_veloth.tga", "miscGazeOfVelothDesc", "damageHealth" }, + T_mysticism_DetEnemy = { "miscDetectEnemy", 1, "td\\s\\td_s_det_enemy.tga", "miscDetectEnemyDesc", "detectAnimal" }, + T_mysticism_DetInvisibility = { "miscDetectInvisibility", 1.5, "td\\s\\td_s_det_invisibility.tga", "miscDetectInvisibilityDesc", "detectAnimal" }, + T_mysticism_Blink = { "miscBlink", 10, "td\\s\\td_s_blink.tga", "miscBlinkDesc", "detectAnimal" }, + T_restoration_FortifyCasting = { "miscFortifyCasting", 1, "td\\s\\td_s_ftfy_cast.tga", "miscFortifyCastingDesc", "fortifyAttack" }, + T_illusion_PrismaticLight = { "miscPrismaticLight", 0.4, "td\\s\\td_s_p_light.tga", "miscPrismaticLightDesc", "light" }, + T_mysticism_BloodMagic = { "miscBloodMagic", 2, "td\\s\\td_s_blood_magic.tga", "miscBloodMagicDesc", "detectAnimal" }, + T_conjuration_SanguineRose = { "miscSanguineRose", 40, "td\\s\\td_s_sanguine.dds.tga", "miscSanguineRoseDesc", "summonDremora" }, + T_mysticism_DetValuables = { "miscDetectValuables", 1.5, "td\\s\\td_s_det_value.tga", "miscDetectValuablesDesc", "detectAnimal" }, + T_mysticism_MagickaWard = { "miscMagickaWard", 20, "td\\s\\td_s_magickaward.tga", "miscMagickaWardDesc", "detectAnimal" }, + T_illusion_Ethereal = { "miscEthereal", 40, "td\\s\\td_s_ethereal.tga", "miscEtherealtDesc", "blind" }, } -- spell id, cast type, spell name, spell mana cost, effect1, ... From 0feef41cedbb47ed6d4c59f84d993acd55a5ab18 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 22 Apr 2026 20:40:23 +0200 Subject: [PATCH 14/28] Turn Passwall into a real effect --- Data Files/Tamriel_Data.omwscripts | 2 +- Data Files/l10n/TamrielData/en.yaml | 48 +++++++++++++++++++ Data Files/l10n/TamrielData/fr.yaml | 48 +++++++++++++++++++ Data Files/l10n/TamrielData/pl.yaml | 43 +++++++++++++++++ ...gic_passwall.lua => global_miscspells.lua} | 28 +++++++++-- Data Files/scripts/TamrielData/load_magic.lua | 28 ++++++++++- .../scripts/TamrielData/player_magic.lua | 46 +++++------------- .../TamrielData/player_magic_passwall.lua | 45 +++++++---------- .../TamrielData/utils/feature_data.lua | 2 +- 9 files changed, 222 insertions(+), 68 deletions(-) rename Data Files/scripts/TamrielData/{global_magic_passwall.lua => global_miscspells.lua} (61%) diff --git a/Data Files/Tamriel_Data.omwscripts b/Data Files/Tamriel_Data.omwscripts index 7aeef41..9e404c1 100644 --- a/Data Files/Tamriel_Data.omwscripts +++ b/Data Files/Tamriel_Data.omwscripts @@ -1,11 +1,11 @@ MENU: scripts/TamrielData/menu_settings.lua GLOBAL: scripts/TamrielData/global_restrict_equipment.lua -GLOBAL: scripts/TamrielData/global_magic_passwall.lua GLOBAL: scripts/TamrielData/global_mwscript_variable.lua PLAYER: scripts/TamrielData/player_magic.lua PLAYER: scripts/TamrielData/player_restrict_equipment.lua MENU: scripts/TamrielData/menu_version_warning.lua GLOBAL: scripts/TamrielData/global_magic.lua +GLOBAL: scripts/TamrielData/global_miscspells.lua GLOBAL: scripts/TamrielData/global_summons.lua PLAYER, NPC, CREATURE: scripts/TamrielData/actor_magic.lua PLAYER, NPC, CREATURE: scripts/TamrielData/actor_summons.lua diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 8dbd6de..31d5639 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -82,6 +82,54 @@ Magic_summonFrostMonarchDesc: "This effect summons a frost monarch from Oblivion Magic_summonSpiderDaedra: "Summon Spider Daedra" Magic_summonSpiderDaedraDesc: "This effect summons a spider daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_miscPasswall: "Passwall" +Magic_miscPasswallDesc: "In an indoor area, this effect permits the caster to pass through a solid barrier to a vacant space behind it. The effect will fail if the destination beyond the traversed barrier is filled with water, is blocked by a forcefield, sigil gate, or ward, or lies above or below the caster." +Magic_miscBanish: "Banish Daedra" +Magic_miscBanishDesc: "Banishes any daedra that the spell is cast upon if the spell's magnitude is greater than or equal to the target's level. If the daedra is wounded, then it will be easier to banish. Banishing a daedra will transfer any of their important belongings to a sigil that is left behind." +Magic_miscReflectDamage: "Reflect Damage" +Magic_miscReflectDamageDesc: "This effect allows the subject to reflect physical damage back at an attacker. The effect's magnitude is the percent damage that will be reflected for each attack. Any unreflected damage is dealt to the defender normally." +Magic_miscDetectHumanoid: "Detect Humanoid" +Magic_miscDetectHumanoidDesc: "The caster of this effect can detect any entity animated by a spirit; they appear on the map as symbols. This effect includes all people. The effect's magnitude is the range in feet from the caster that humanoids are detected." +Magic_miscRadiantShield: "Radiant Shield" +Magic_miscRadiantShieldDesc: "This effect creates a shield of brilliant light around the subject's entire body. The spell adds its magnitude to the subject's Armor Rating, resists harmful magic, and briefly blinds attackers in melee." +Magic_miscRadiantShieldBlindness: "Blinding Radiance" +Magic_miscWabbajack: "Wabbajack" +Magic_miscWabbajackDesc: "Wabbajack!" +Magic_miscInsight: "Insight" +Magic_miscInsightDesc: "This effect lightly twists fate, increasing the chance of discovering valuable items." +Magic_miscArmorResartus: "Armor Resartus" +Magic_miscArmorResartusDesc: "This effect mends and recharges enchanted armor that is equipped by the caster. The magnitude is the units of condition and charge restored, which are distributed across all of the caster's enchanted armor." +Magic_miscWeaponResartus: "Weapon Resartus" +Magic_miscWeaponResartusDesc: "This effect mends and recharges an enchanted weapon that is equipped by the caster. The magnitude is the units of condition and charge restored." +Magic_miscCorruption: "Corruption" +Magic_miscCorruptionDesc: "This effect creates a shadowy counterpart of the target that will aid the caster in combat." +Magic_miscDistractCreature: "Distract Creature" +Magic_miscDistractCreatureDesc: "This effect compels a creature to wander away from their current position while attempting to keep their distance from the caster. The effect's magnitude is the maximum distance that the target can travel and the effect cannot be casted again on the target while it is active. Using this effect will fail if the target is aware of the caster's presence. When the effect ends, the target begins to return to their original location and cannot be distracted again until they do." +Magic_miscDistractHumanoid: "Distract Humanoid" +Magic_miscDistractHumanoidDesc: "This effect compels a person to wander away from their current position while attempting to keep their distance from the caster. The effect's magnitude is the maximum distance that the target can travel and the effect cannot be casted again on the target while it is active. Using this effect will fail if the target is aware of the caster's presence. When the effect ends, the target begins to return to their original location and cannot be distracted again until they do." +Magic_miscGazeOfVeloth: "Gaze of Veloth" +Magic_miscGazeOfVelothDesc: "Witness the Face of Veloth!" +Magic_miscDetectEnemy: "Detect Enemy" +Magic_miscDetectEnemyDesc: "The caster of this effect can detect any entity animated by a spirit; they appear on the map as symbols. This effect includes all hostile beings. The effect's magnitude is the range in feet from the caster that enemies are detected." +Magic_miscDetectInvisibility: "Detect Invisibility" +Magic_miscDetectInvisibilityDesc: "The caster of this effect can detect any entity animated by a spirit; they appear on the map as symbols. This effect includes all beings affected by chameleon or invisibility effects. The effect's magnitude is the range in feet from the caster that hidden beings are detected. The chameleon and invisibility effects on detected entities are also weakened." +Magic_miscBlink: "Blink" +Magic_miscBlinkDesc: "This effect teleports the caster in whatever direction they are looking in. The effect's magnitude is the maximum distance that the caster can move." +Magic_miscFortifyCasting: "Fortify Casting" +Magic_miscFortifyCastingDesc: "This effect raises the subject's chance of successfully casting a spell." +Magic_miscPrismaticLight: "Prismatic Light" +Magic_miscPrismaticLightDesc: "This effect creates a projectile of prismatic light. Upon striking a target, the projectile illuminates the area for the duration of the effect. The light projectile does not cause any damage." +Magic_miscBloodMagic: "Blood Magic" +Magic_miscBloodMagicDesc: "Placeholder" +Magic_miscSanguineRose: "Sanguine Rose" +Magic_miscSanguineRoseDesc: "This effect summons a random daedra from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." +Magic_miscDetectValuables: "Detect Valuables" +Magic_miscDetectValuablesDesc: "The caster of this effect can detect items valued at 4000 drakes or more; they appear on the map as symbols. The effect's magnitude is the range in feet from the caster that items are detected." +Magic_miscMagickaWard: "Magicka Ward" +Magic_miscMagickaWardDesc: "Placeholder" +Magic_miscEthereal: "Ethereal" +Magic_miscEtherealDesc: "The caster of this effect becomes incorporeal and can pass through other entities. The caster is incapable of being harmed by them, but also cannot act upon them, cast magic, use items, or physically interact with the world in general. The caster cannot pass through objects such as walls and still obeys the laws of gravity." + Magic_itemPotionReflectDamageB: "Bargain Potion of Reflect Dmg" Magic_itemPotionReflectDamageC: "Cheap Potion of Reflect Dmg" Magic_itemPotionReflectDamageS: "Standard Potion of Reflect Dmg" diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index d424da7..e9fd2ae 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -82,6 +82,54 @@ Magic_summonFrostMonarchDesc: "Cet effet permet d'invoquer un monarque de givre Magic_summonSpiderDaedra: "Appel d'araignée daedra" Magic_summonSpiderDaedraDesc: "Cet effet permet d'invoquer un monarque de givre des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_miscPasswall: "Passe-muraille" +Magic_miscPasswallDesc: "En intérieur, cet effet permet au lanceur de traverser une barrière solide vers un espace vide derrière celle-ci. L'effet échouera si la destination au-delà de la barrière traversée est emplie d'eau, bloquée par un champ de force, une porte scellée ou une barrière, ou se trouve au-dessus ou au-dessous du lanceur." +Magic_miscBanish: "Bannissement de Daedra" +Magic_miscBanishDesc: "Bannit tout Daedra sur lequel ce sort est lancé, à condition que la cible soit d'un niveau suffisamment bas comparé à la puissance du sort. Si le Daedra est blessé, il sera plus facile à bannir. Bannir un Daedra transfert tous ses biens d'importance dans un sceau laissé à sa place." +Magic_miscReflectDamage: "Réflexion de dégâts" +Magic_miscReflectDamageDesc: "Cet effet permet de renvoyer des dégâts physiques à un agresseur. La puissance de l'effet détermine le pourcentage de dégâts qui sera reflété pour chaque attaque. Tout dégât non réfléchi affecte normalement sa cible." +Magic_miscDetectHumanoid: "Détection des humanoïdes" +Magic_miscDetectHumanoidDesc: "Cet effet permet de détecter n'importe quelle entité animée par un esprit ; elles apparaissent sous forme de symboles sur la carte. Cet effet comprend tous les personnages. La puissance de l'effet détermine la portée à laquelle le lanceur peut détecter des individus (en pieds).", +Magic_miscRadiantShield: "Bouclier radieux" +Magic_miscRadiantShieldDesc: "Cet effet crée un bouclier de lumière brillante autour du sujet. La puissance de l'effet s'ajoute à la valeur d'armure du sujet, réduit considérablement les effets des attaques magiques, et aveugle brièvement les agresseurs au corps-à-corps." +Magic_miscRadiantShieldBlindness: "Brillance aveuglante" +Magic_miscWabbajack: "Wabbajack" +Magic_miscWabbajackDesc: "Wabbajack !" +Magic_miscInsight: "Perspicacité" +Magic_miscInsightDesc: "Cet effet influe légèrement sur le destin en augmentant les chances de découvrir des objets de valeur." +Magic_miscArmorResartus: "Armure raccommodée" +Magic_miscArmorResartusDesc: "Cet effet répare et recharge l'armure enchantée équipée par le lanceur. La puissance de l'effet correspond au nombre d'unités de condition et de charge restaurées, qui sont distribuées entre chacun des pièces d'armure enchantées du lanceur." +Magic_miscWeaponResartus: "Arme raccommodée" +Magic_miscWeaponResartusDesc: "Cet effet répare et recharge l'arme enchantée équipée par le lanceur. La puissance de l'effet correspond au nombre d'unités de condition et de charge restaurées." +Magic_miscCorruption: "Corruption" +Magic_miscCorruptionDesc: "Cet effet crée un double ténébreux de la cible qui aidera le lanceur au combat." +Magic_miscDistractCreature: "Distraction des créatures" +Magic_miscDistractCreatureDesc: "Cet effet incite une créature à s'éloigner de sa position actuelle tout en tentant de garder ses distances par rapport au lanceur. La puissance de l'effet correspond à la distance maximale à laquelle la cible peut s'éloigner de sa position, et l'effet ne peut être lancé à nouveau sur la cible tant qu'il est actif. Cet effet échouera si la cible a conscience de la présence du lanceur. Quand l'effet prend fin, la cibl commence à rejoindre sa position initiale et ne peut être distraite à nouveau jusqu'à ce qu'elle l'atteigne." +Magic_miscDistractHumanoid: "Distraction des humanoïdes" +Magic_miscDistractHumanoidDesc: "Cet effet incite une personne à s'éloigner de sa position actuelle tout en tentant de garder ses distances par rapport au lanceur. La puissance de l'effet correspond à la distance maximale à laquelle la cible peut s'éloigner de sa position, et l'effet ne peut être lancé à nouveau sur la cible tant qu'il est actif. Cet effet échouera si la cible a conscience de la présence du lanceur. Quand l'effet prend fin, la cible commence à rejoindre sa position initiale et ne peut être distraite à nouveau jusqu'à ce qu'elle l'atteigne." +Magic_miscGazeOfVeloth: "Regard de Véloth" +Magic_miscGazeOfVelothDesc: "Soyez témoin du visage de Véloth !" +Magic_miscDetectEnemy: "Détection des ennemis" +Magic_miscDetectEnemyDesc: "Cet effet permet de détecter n'importe quelle entité hostile ; elles apparaissent sous forme de symboles sur la carte. La puissance de l'effet détermine la portée à laquelle le lanceur peut détecter des entités hostiles (en pieds)." +Magic_miscDetectInvisibility: "Détection d'invisibilité" +Magic_miscDetectInvisibilityDesc: "Cet effet permet de détecter n'importe quelle entité affectée par un effet d'invisibilité ou de caméléon ; elles apparaissent sous forme de symboles sur la carte. La puissance de l'effet détermine la portée à laquelle le lanceur peut détecter des entités dissimulées (en pieds). Les effets d'invisibilité et de caméléon des entités détectées sont également affaiblis." +Magic_miscBlink: "Transfert" +Magic_miscBlinkDesc: "Cet effet téléporte le lanceur dans la direction vers laquelle il regarde. La puissance de l'effet correspond à la distance maximale qui peut être parcourue." +Magic_miscFortifyCasting: "Sorts fortifiés" +Magic_miscFortifyCastingDesc: "Cet effet augmente la chance du sujet de parvenir à lancer un sort." +Magic_miscPrismaticLight: "Lumière prismatique" +Magic_miscPrismaticLightDesc: "Cet effet crée un projectile de lumière prismatique. Quand il touche une cible, le projectile illumine la zone pour la durée de l'effet. Le projectile de lumière n'inflige aucun dégât." +Magic_miscBloodMagic: "Magie du sang" +Magic_miscBloodMagicDesc: "Placeholder" +Magic_miscSanguineRose: "Rose de Sanguiyn" +Magic_miscSanguineRoseDesc: "Cet effet permet d'invoquer un Daedra aléatoire des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." +Magic_miscDetectValuables: "Détection des objets précieux" +Magic_miscDetectValuablesDesc: "Cet effet permet de détecter les objets précieux valant au moins 4000 drakes ; ils apparaissent sous forme de symboles sur la carte. La puissance de l'effet détermine la portée à laquelle le lanceur peut détecter des objets précieux (en pieds)." +Magic_miscMagickaWard: "Barrière magique" +Magic_miscMagickaWardDesc: "Placeholder" +Magic_miscEthereal: "Forme éthérée" +Magic_miscEtherealDesc: "Le lanceur de cet effet revêt une forme incorporelle et peut traverser les autres entités. Le lanceur ne peut être blessé par celles-ci, mais ne peut pas non plus agir sur elles, lancer des sorts, utiliser des objets, ou généralement interagir physiquement avec le monde. Le lanceur ne peut pas traverser des objets comme des murs et obéit toujours aux lois de la gravité." + Magic_itemPotionReflectDamageB: "Potion Réflexion dégâts bradée" Magic_itemPotionReflectDamageC: "Potion Réflexion dégâts bas prix" Magic_itemPotionReflectDamageS: "Potion Réflexion dégâts std" diff --git a/Data Files/l10n/TamrielData/pl.yaml b/Data Files/l10n/TamrielData/pl.yaml index a4f06c8..d439009 100644 --- a/Data Files/l10n/TamrielData/pl.yaml +++ b/Data Files/l10n/TamrielData/pl.yaml @@ -60,6 +60,49 @@ Magic_summonFrostMonarchDesc: "Przywołuje z Otchłani monarchę mrozu. Stworzen Magic_summonSpiderDaedra: "Przywołanie Pajęczej Daedry" Magic_summonSpiderDaedraDesc: "Przywołuje z Otchłani pajęczą daedrę. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_miscPasswall: "Przeniknięcie Ściany" +Magic_miscPasswallDesc: "Pozwala przeniknąć przez stałe bariery w obszarach wewnętrznych do wolnej przestrzeni poza nimi. Efekt zawiedzie, jeśli miejsce docelowe jest wypełnione wodą, położone jest poza polem siłowym, zapieczętowaną bramą, lub znajduje się powyżej lub poniżej poziomu, na któym znajduje się rzucający zaklęcie." +Magic_miscBanish: "Wypędzenie Daedry" +Magic_miscBanishDesc: "Wypędza daedrę będącą celem zaklęcia, jeśli jej poziom jest wystarczająco niski w porównaniu ze stopniem efektu. Odniesione przez daedrę obrażenia powodują większą podatność na wypędzenie. W przypadku sukcesu ważniejszy dobytek daedry znajdzie się w pozostawionej pieczęci." +Magic_miscReflectDamage: "Odbicie Obrażeń" +Magic_miscReflectDamageDesc: "Odbija fizyczne obrażenia w stronę atakującego. Stopień efektu określa procentową ilość odbitych obrażeń dla każdego ataku. Nieodbite obrażenia odniosą normalny skutek." +Magic_miscDetectHumanoid: "Wykrycie Humanoida" +Magic_miscDetectHumanoidDesc: "Rzucający zaklęcie może wykrywać na odległość obecność istot humanoidalnych. Zostaną one oznaczone na mapie specjalnym symbolem. Efekt obejmuje wszystkie postacie, a jego poziom to promień (w stopach) obszaru, który zostanie magicznie przeszukany." +Magic_miscRadiantShield: "Tarcza Blasku" +Magic_miscRadiantShieldDesc: "Tworzy świetlistą osłonę dookoła celu. Stopień efektu dodawany jest do klasy pancerza. Odporność celu na szkodliwe efekty magiczne wzrasta. Dodatkowo, krótkotrwale oślepia przeciwników w zwarciu." +Magic_miscWabbajack: "Łabadżak" +Magic_miscWabbajackDesc: "Łabadżak!" +Magic_miscInsight: "Intuicja" +Magic_miscInsightDesc: "Nieznacznie wypacza los, zwiększając szansę na znalezienie wartościowych przedmiotów." +Magic_miscArmorResartus: "Odnowienie Pancerza" +Magic_miscArmorResartusDesc: "Odnawia wyposażony zaklęty pancerz. Stopień efektu odpowiada ilości ładunku oraz stanu, która zostanie przywrócona, rozdzielając punkty pomiędzy wszyskie wyposażone zaklęte elementy pancerza." +Magic_miscWeaponResartus: "Odnowienie Broni" +Magic_miscWeaponResartusDesc: "Odnawia wyposażony zaklęty oręż. Stopień efektu odpowiada ilości ładunku oraz stanu, która zostanie przywrócona." +Magic_miscCorruption: "Wypaczenie" +Magic_miscCorruptionDesc: "Tworzy cienisty odpowiednik celu, który wspomoże rzucającego zaklęcie w walce." +Magic_miscDistractCreature: "Rozproszenie Istoty" +Magic_miscDistractCreatureDesc: "Zmusza istotę do oddalenia się z obecnej pozycji, jednocześnie próbując zachować dystans od rzucającego zaklęcie. Stopień efektu określa maksymalną odległość (w stopach), jaką cel może przebyć. Jeśli cel jest już pod wpływem efektu, nie może być rzucony ponownie. Efekt zawiedzie, jeśli cel jest świadomy obecności rzucającego zaklęcie. Po zakończeniu trwania efektu, cel nie może zostać rozproszony, dopóki nie powróci do oryginalnej pozycji." +Magic_miscDistractHumanoid: "Rozproszenie Humanoida" +Magic_miscDistractHumanoidDesc: "Zmusza osobę do oddalenia się z obecnej pozycji, jednocześnie próbując zachować dystans od rzucającego zaklęcie. Stopień efektu określa maksymalną odległość (w stopach), jaką cel może przebyć. Jeśli cel jest już pod wpływem efektu, nie może być rzucony ponownie. Efekt zawiedzie, jeśli cel jest świadomy obecności rzucającego zaklęcie. Po zakończeniu trwania efektu, cel nie może zostać rozproszony, dopóki nie powróci do oryginalnej pozycji." +Magic_miscGazeOfVeloth: "Wejrzenie Velotha" +Magic_miscGazeOfVelothDesc: "Doświadcz Oblicza Velotha!" +Magic_miscDetectEnemy: "Wykrycie Przeciwnika" +Magic_miscDetectEnemyDesc: "Rzucający zaklęcie może wykrywać na odległość obecność istot niemechanicznych. Zostaną one oznaczone na mapie specjalnym symbolem. Efekt obejmuje wszystkich przeciwników, a jego poziom to promień (w stopach) obszaru, który zostanie magicznie przeszukany." +Magic_miscDetectInvisibility: "Wykrycie Niewidzialności" +Magic_miscDetectInvisibilityDesc: "Rzucający zaklęcie może wykrywać na odległość obecność istot niemechanicznych. Zostaną one oznaczone na mapie specjalnym symbolem. Efekt obejmuje wszystkie istoty pod wpływem efektu kameleona lub niewidzialności, a jego poziom to promień (w stopach) obszaru, który zostanie magicznie przeszukany. Wykryte efekty kameleona oraz niewidzialności zostaną również osłabione." +Magic_miscBlink: "Migotanie" +Magic_miscBlinkDesc: "Teleportuje rzucającego zaklęcie w kierunku, w który patrzy. Stopień efektu określa maksymalną odległość (w stopach), jaki cel może przebyć." +Magic_miscFortifyCasting: "Premia do Magii" +Magic_miscFortifyCastingDesc: "Tymczasowo zwiększa szanse postaci na skuteczne rzucenie zaklęcia." +Magic_miscPrismaticLight: "Pryzmatyczne Światło" +Magic_miscPrismaticLightDesc: "Tworzy magiczny pocisk, który po uderzeniu w cel rozświetla na pewien czas okolicę. Nie zadaje obrażeń." +Magic_miscBloodMagic: "Magia Krwi" +Magic_miscBloodMagicDesc: "Tekst zastępczy" +Magic_miscSanguineRose: "Róża Sanguine'a" +Magic_miscSanguineRoseDesc: "Przywołuje z Otchłani losową daedrę. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" +Magic_miscDetectValuables: "Wykrycie Kosztowności" +Magic_miscDetectValuablesDesc: "Rzucający zaklęcie może wykrywać na odległość kosztowne przedmioty. Stopień efektu określa promień (w stopach) obszaru, który zostanie magicznie przeszukany." + Magic_itemPotionReflectDamageB: "Słaba mikstura odbicia obrażeń" Magic_itemPotionReflectDamageC: "Mała mikstura odbicia obrażeń" Magic_itemPotionReflectDamageS: "Klasyczne odbicie obrażeń" diff --git a/Data Files/scripts/TamrielData/global_magic_passwall.lua b/Data Files/scripts/TamrielData/global_miscspells.lua similarity index 61% rename from Data Files/scripts/TamrielData/global_magic_passwall.lua rename to Data Files/scripts/TamrielData/global_miscspells.lua index aa94041..b6ab059 100644 --- a/Data Files/scripts/TamrielData/global_magic_passwall.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -1,10 +1,15 @@ -if not require("scripts.TamrielData.utils.version_check").isFeatureSupported("miscSpells") then +local core = require('openmw.core') +local passwallEffect = core.magic.effects.records['T_mysticism_Passwall'] + +if not passwallEffect then return end +local I = require('openmw.interfaces') local types = require('openmw.types') local world = require('openmw.world') -local crimes = require('openmw.interfaces').Crimes + +local passwall_target_effect_model = types.Static.records[passwallEffect.hitStatic].model local function triggerCrimeIfTrespassing(data) if not data.targetObject or not data.targetObject.owner or not types.Lockable.isLocked(data.targetObject) then @@ -25,7 +30,7 @@ local function triggerCrimeIfTrespassing(data) (ownerData.factionId and types.NPC.getFactionRank(data.player, ownerData.factionId) < (ownerData.factionRank or 1)) if isTrespassing then - crimes.commitCrime( + I.Crimes.commitCrime( data.player, { faction = ownerData.factionId, @@ -40,10 +45,25 @@ local function teleportPlayer(data) triggerCrimeIfTrespassing(data) - local passwall_target_effect_model = types.Static.records[data.vfxStatic].model world.vfx.spawn(passwall_target_effect_model, data.position) end +local onStart = { + t_mysticism_passwall = function(caster, spell, effect, track) + if types.Player.objectIsInstance(caster) then + track.ignore = false + caster:sendEvent('T_Passwall_Cast', effect.magnitudeThisFrame) + end + end +} + +I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) + local handler = onStart[effect.id] + if handler then + handler(caster, spell, effect, track) + end +end) + return { eventHandlers = { T_Passwall_teleportPlayer = teleportPlayer, diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 66872d9..9cd4c9d 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -32,7 +32,14 @@ local function parseEffects(values, offset) break end effects[i] = row - row.range = RANGE[row.range] + if row.id == 'T_mysticism_Passwall' then + row.range = RANGE.self + row.magnitudeMin = row.area + row.magnitudeMax = row.area + row.area = nil + else + row.range = RANGE[row.range] + end end return implemented, effects end @@ -168,13 +175,32 @@ local function addSummons() end end +local function addMiscEffects() + local effects = content.magicEffects.records + local function addMiscEffect(id, params) + local name, cost, icon, desc, template = unpack(magicData.td_misc_effects[id]) + params.name = t(name) + params.cost = cost + params.icon = icon + params.description = t(desc) + params.template = effects[template] + effects[id] = params + implementedEffects[id] = true + end + addMiscEffect('T_mysticism_Passwall', { onTarget = false, onTouch = false, hasDuration = false }) +end + return { engineHandlers = { onContentFilesLoaded = function() if version_check.isFeatureEnabled('summoningSpells') then addSummons() end + if version_check.isFeatureEnabled('miscSpells') then + addMiscEffects() + end replaceSpells(magicData.td_summon_spells) + replaceSpells(magicData.td_misc_spells) replaceEnchantments(magicData.td_enchantments) editItems(magicData.td_enchanted_items) replacePotions(magicData.td_potions) diff --git a/Data Files/scripts/TamrielData/player_magic.lua b/Data Files/scripts/TamrielData/player_magic.lua index f5ace98..8b52822 100644 --- a/Data Files/scripts/TamrielData/player_magic.lua +++ b/Data Files/scripts/TamrielData/player_magic.lua @@ -1,33 +1,13 @@ -local types = require('openmw.types') -local self = require('openmw.self') -local version_check = require("scripts.TamrielData.utils.version_check") -local magic_passwall = require("scripts.TamrielData.player_magic_passwall") - --- Run a perpetual check for any active spells which need a TD override - -local checkFrequency = 0.5 -- No need to check for magic that often -local checkCounter = 0.0 - -local function checkForAnyActiveSpells(timeSinceLastCheck) - checkCounter = checkCounter + timeSinceLastCheck - if checkCounter < checkFrequency then - return - end - checkCounter = 0 - for _, spell in pairs(types.Actor.activeSpells(self)) do - if spell.id == "t_com_mys_uni_passwall" then - if version_check.isFeatureEnabled("miscSpells") then - if magic_passwall then - types.Actor.activeSpells(self):remove(spell.activeSpellId) - magic_passwall.onCastPasswall() - end - end - end - end -end - -return { - engineHandlers = { - onUpdate = checkForAnyActiveSpells - } -} \ No newline at end of file +local core = require('openmw.core') + +if not core.magic.effects.records['T_mysticism_Passwall'] then + return +end + +local magic_passwall = require('scripts.TamrielData.player_magic_passwall') + +return { + eventHandlers = { + T_Passwall_Cast = magic_passwall.onCastPasswall + } +} diff --git a/Data Files/scripts/TamrielData/player_magic_passwall.lua b/Data Files/scripts/TamrielData/player_magic_passwall.lua index 8690c23..ddcdb33 100644 --- a/Data Files/scripts/TamrielData/player_magic_passwall.lua +++ b/Data Files/scripts/TamrielData/player_magic_passwall.lua @@ -1,7 +1,3 @@ -if not require("scripts.TamrielData.utils.version_check").isFeatureSupported("miscSpells") then - return -end - local self = require('openmw.self') local ambient = require('openmw.ambient') local async = require('openmw.async') @@ -15,11 +11,10 @@ local l10n = core.l10n("TamrielData") local debug = require("scripts.TamrielData.utils.debug_logging") local FT_TO_UNITS = 22.1 -local maxSpellDistance = 25 * FT_TO_UNITS -- 25ft is a default Passwall spell range in the MWSE version -local maxSpellDistanceSquared = maxSpellDistance * maxSpellDistance local veryCloseSquared = 11 * 11 -- an arbitrary value representing a close enough object that no wall is between local passwallSpellId = "t_com_mys_uni_passwall" -local passwallFailureSound = core.stats.Skill.records[core.magic.spells.records[passwallSpellId].effects[1].effect.school].school.failureSound +local passwallFailureSound = core.stats.Skill.records[core.magic.effects.records.T_mysticism_Passwall.school].school.failureSound +local activationDistance = core.getGMST("iMaxActivateDist") + 0.1 local function calculatePlayerHeight() local playerRecord = types.NPC.record(self) @@ -38,17 +33,11 @@ local function getActivationVector() return util.vector3(cameraVector.x, cameraVector.y, 0.0):normalize() end -local function getActivationDistance() - return core.getGMST("iMaxActivateDist") + 0.1 -end - local function getRaycastingInputData() local activationVector = getActivationVector() - local activateDistance = getActivationDistance() return { startPos = self.position + util.vector3(0, 0, calculatePlayerHeight() * 0.7), -- castPosition as in MWSE version - directionVector = activationVector, - activateDistance = activateDistance + directionVector = activationVector } end @@ -59,8 +48,7 @@ local function startTeleporting(newPosition, newCell, newRotation, targetObject) position = newPosition, cell = newCell, rotation = newRotation, - targetObject = targetObject, - vfxStatic = core.magic.spells.records[passwallSpellId].effects[1].effect.hitStatic + targetObject = targetObject }) end @@ -186,7 +174,7 @@ local function isBlockedByIllegalActivator(object) return false end -local function isThereAReachableItemFromPosition(startPosition) +local function isThereAReachableItemFromPosition(startPosition, maxSpellDistanceSquared) -- If no other check passed until now, then perhaps there is nothing of interest except items in that area. -- In that case a reachable item hopefully is close by, so no need for far checks. @@ -203,7 +191,7 @@ local function isThereAReachableItemFromPosition(startPosition) return false end -local function isCalculatedPositionIntendedForThePlayer(position) +local function isCalculatedPositionIntendedForThePlayer(position, range) if not position then return false end @@ -231,7 +219,7 @@ local function isCalculatedPositionIntendedForThePlayer(position) return true end end - return isThereAReachableItemFromPosition(position) + return isThereAReachableItemFromPosition(position, range * range) end local function isRayHitOnBlocker(rayHit) @@ -239,7 +227,7 @@ local function isRayHitOnBlocker(rayHit) return isBlockedByWard(object) or isBlockedByIllegalActivator(object) end -local function calculatePasswallPosition(intermediateRayHits, limitingPosition, directionVector) +local function calculatePasswallPosition(intermediateRayHits, limitingPosition, directionVector, range) local rayTestOffset = 19 -- We could say that a 2*19 square is enough to fit the player in local minDistanceSquared = 108 * 108 -- minDistance from the MWSE version but squared local maxZDifference = 105 -- upCoord from the MWSE version @@ -300,7 +288,7 @@ local function calculatePasswallPosition(intermediateRayHits, limitingPosition, local isIntendedForThePlayer = isPositionNotTooClose and isPositionNotTooHighOrLow and isPositionNotTooFar if isIntendedForThePlayer then - isIntendedForThePlayer = isCalculatedPositionIntendedForThePlayer(navMeshPosition) + isIntendedForThePlayer = isCalculatedPositionIntendedForThePlayer(navMeshPosition, range) end if isIntendedForThePlayer then return navMeshPosition @@ -319,10 +307,10 @@ local function calculatePasswallPosition(intermediateRayHits, limitingPosition, return nil end -local function gatherAllRayHitsAndLimitingPosition(raycastingInputData, firstRaycastHit) - local remainingTeleportDistance = maxSpellDistance +local function gatherAllRayHitsAndLimitingPosition(raycastingInputData, firstRaycastHit, range) + local remainingTeleportDistance = range local intermediateRayHits = {firstRaycastHit} - local limitingPosition = firstRaycastHit.hitPos + raycastingInputData.directionVector * maxSpellDistance + local limitingPosition = firstRaycastHit.hitPos + raycastingInputData.directionVector * range local previousRaycastSourceObjects = {} while remainingTeleportDistance > 0 do @@ -348,7 +336,8 @@ end local PSW = {} -function PSW.onCastPasswall() +function PSW.onCastPasswall(magnitude) + local range = math.max(magnitude, 0) * FT_TO_UNITS + activationDistance debug.log( string.format( "START: pos:%s, cell:%s, rotation:%s, race:%s, isMale:%s, navHalfExtents:%s, navShapeType:%s", @@ -375,7 +364,7 @@ function PSW.onCastPasswall() end local raycastingInputData = getRaycastingInputData() - local raycastingEnd = raycastingInputData.startPos + raycastingInputData.directionVector * raycastingInputData.activateDistance + local raycastingEnd = raycastingInputData.startPos + raycastingInputData.directionVector * activationDistance local firstRaycastHit = nearby.castRay( raycastingInputData.startPos, @@ -411,11 +400,11 @@ function PSW.onCastPasswall() return onPasswallFail() end - local intermediateRayHits, limitingPosition = gatherAllRayHitsAndLimitingPosition(raycastingInputData, firstRaycastHit) + local intermediateRayHits, limitingPosition = gatherAllRayHitsAndLimitingPosition(raycastingInputData, firstRaycastHit, range) -- intermediateRayHits include the first raycast hit (a spell target object, i.e. wall) as element [1] -- limitingPosition is the max distance the spell could reach: should be farther from the player than (or as far as) all intermediateRayHits - local finalTeleportPosition = calculatePasswallPosition(intermediateRayHits, limitingPosition, raycastingInputData.directionVector) + local finalTeleportPosition = calculatePasswallPosition(intermediateRayHits, limitingPosition, raycastingInputData.directionVector, range) if finalTeleportPosition then startTeleporting(finalTeleportPosition, self.cell.name, self.rotation, targetObject) diff --git a/Data Files/scripts/TamrielData/utils/feature_data.lua b/Data Files/scripts/TamrielData/utils/feature_data.lua index 5331ef0..11bdc25 100644 --- a/Data Files/scripts/TamrielData/utils/feature_data.lua +++ b/Data Files/scripts/TamrielData/utils/feature_data.lua @@ -13,7 +13,7 @@ features["restrictEquipment"] = { settingsKey = "Settings_TamrielData_page01Main_group01Main_restrictEquipment" } features["miscSpells"] = { - requiredLuaApi = 71, + requiredLuaApi = 129, settingsPlayerSectionStorageId = "Settings_TamrielData_page01Main_group02Magic", settingsEnabledByDefault = true, settingsKey = "Settings_TamrielData_page01Main_group02Magic_miscSpells" From b716f4b45e67bc2d46aebd2dbb024189b2fa0ee6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 23 Apr 2026 19:42:45 +0200 Subject: [PATCH 15/28] Implement Reflect Damage --- .../scripts/TamrielData/actor_magic.lua | 63 +++++++++++++++++++ .../scripts/TamrielData/global_miscspells.lua | 5 +- Data Files/scripts/TamrielData/load_magic.lua | 13 +++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index b405ce6..78810fd 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -1,5 +1,68 @@ local core = require('openmw.core') +local I = require('openmw.interfaces') local self = require('openmw.self') +local auxUtil = require('openmw_aux.util') + +local activeEffects = self.type.activeEffects(self) +local activeSpells = self.type.activeSpells(self) + +local function calculateReflect(health, fatigue) + local reflectedHealth = 0 + local reflectedFatigue = 0 + for _, spell in pairs(activeSpells) do + for _, effect in pairs(spell.effects) do + if effect.id == 't_mysticism_reflectdmg' then + local mult = effect.magnitudeThisFrame / 100 + reflectedHealth = reflectedHealth + health * mult + health = health * (1 - mult) + reflectedFatigue = reflectedFatigue + fatigue * mult + fatigue = fatigue * (1 - mult) + end + end + end + if health <= 0 then + health = nil + end + if fatigue <= 0 then + fatigue = nil + end + if reflectedHealth <= 0 then + reflectedHealth = nil + end + if reflectedFatigue <= 0 then + reflectedFatigue = nil + end + return health, fatigue, reflectedHealth, reflectedFatigue +end + +I.Combat.addOnHitHandler(function(attack) + if not attack.successful or not attack.damage or not attack.attacker or not attack.attacker:isValid() then + return + elseif attack.sourceType ~= I.Combat.ATTACK_SOURCE_TYPES.Melee and attack.sourceType ~= I.Combat.ATTACK_SOURCE_TYPES.Ranged then + return + end + local health = attack.damage.health or 0 + local fatigue = attack.damage.fatigue or 0 + if health <= 0 and fatigue <= 0 then + return + elseif activeEffects:getEffect('t_mysticism_reflectdmg').magnitude <= 0 then + return + end + local newHealth, newFatigue, reflectedHealth, reflectedFatigue = calculateReflect(health, fatigue) + local reflectedAttack = auxUtil.shallowCopy(attack) + reflectedAttack.attacker = self.object + reflectedAttack.sourceType = I.Combat.ATTACK_SOURCE_TYPES.Unspecified + reflectedAttack.damage = {} + if attack.damage.health then + attack.damage.health = newHealth + reflectedAttack.damage.health = reflectedHealth + end + if attack.damage.fatigue then + attack.damage.fatigue = newFatigue + reflectedAttack.damage.fatigue = reflectedFatigue + end + attack.attacker:sendEvent('Hit', reflectedAttack) +end) return { engineHandlers = { diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index b6ab059..9bd9d96 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -54,7 +54,10 @@ local onStart = { track.ignore = false caster:sendEvent('T_Passwall_Cast', effect.magnitudeThisFrame) end - end + end, + t_mysticism_reflectdmg = function(caster, spell, effect, track) + track.ignore = false + end, } I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 9cd4c9d..d57658e 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -39,7 +39,11 @@ local function parseEffects(values, offset) row.area = nil else row.range = RANGE[row.range] + row.magnitudeMin = row.min + row.magnitudeMax = row.max end + row.min = nil + row.max = nil end return implemented, effects end @@ -180,14 +184,19 @@ local function addMiscEffects() local function addMiscEffect(id, params) local name, cost, icon, desc, template = unpack(magicData.td_misc_effects[id]) params.name = t(name) - params.cost = cost + params.cost = params.cost or cost params.icon = icon params.description = t(desc) params.template = effects[template] effects[id] = params implementedEffects[id] = true end - addMiscEffect('T_mysticism_Passwall', { onTarget = false, onTouch = false, hasDuration = false }) + addMiscEffect('T_mysticism_Passwall', { + onTarget = false, onTouch = false, hasDuration = false, + cost = magicData.td_misc_effects.T_mysticism_Passwall[2] * 0.5 -- compensate for MWSE using area instead of magnitude + }) + --addMiscEffect('T_mysticism_BanishDae', {}) + addMiscEffect('T_mysticism_ReflectDmg', {}) end return { From dfca7eec55f5fcc1929837daf51697a522dc6a1a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 23 Apr 2026 20:23:49 +0200 Subject: [PATCH 16/28] Implement Fortify Casting --- .../scripts/TamrielData/global_miscspells.lua | 70 +++++++++++++++++-- Data Files/scripts/TamrielData/load_magic.lua | 10 ++- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index 9bd9d96..3f77b93 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -48,26 +48,84 @@ local function teleportPlayer(data) world.vfx.spawn(passwall_target_effect_model, data.position) end +local function toKey(actor, id, index) + return actor.id .. ',' .. id .. ',' .. index +end + +local state = { + effects = {} +} + +local function store(target, spell, effect) + local id = spell.activeSpellId + local index = effect.index + local key = toKey(target, id, index) + state.effects[key] = { id = id, index = index, actor = target, magnitude = effect.magnitudeThisFrame, effect = effect.id } +end + local onStart = { - t_mysticism_passwall = function(caster, spell, effect, track) - if types.Player.objectIsInstance(caster) then + t_mysticism_passwall = function(target, spell, effect, track) + if types.Player.objectIsInstance(target) then track.ignore = false - caster:sendEvent('T_Passwall_Cast', effect.magnitudeThisFrame) + target:sendEvent('T_Passwall_Cast', effect.magnitudeThisFrame) end end, - t_mysticism_reflectdmg = function(caster, spell, effect, track) + t_mysticism_reflectdmg = function(target, spell, effect, track) + track.ignore = false + end, + t_restoration_fortifycasting = function(target, spell, effect, track) + local activeEffects = target.type.activeEffects(target) + activeEffects:modify(-effect.magnitudeThisFrame, 'sound') + store(target, spell, effect) track.ignore = false end, } -I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) +local onEnd = { + t_restoration_fortifycasting = function(effect) + local activeEffects = effect.actor.type.activeEffects(effect.actor) + activeEffects:modify(effect.magnitude, 'sound') + end +} + +I.T_ActorMagic.addEffectStartHandler(function(target, spell, effect, track) local handler = onStart[effect.id] if handler then - handler(caster, spell, effect, track) + handler(target, spell, effect, track) + end +end) + +I.T_ActorMagic.addEffectEndHandler(function(actor, id, index) + local key = toKey(actor, id, index) + local effect = state.effects[key] + if effect then + local handler = onEnd[effect.effect] + if handler then + handler(effect) + end + state.effects[key] = nil end end) return { + engineHandlers = { + onSave = function() + return state + end, + onLoad = function(data) + if data then + state = data + local effects = {} + for _, actorData in pairs(data.effects) do + if actorData.actor:isValid() then + local key = toKey(actorData.actor, actorData.id, actorData.index) + effects[key] = actorData + end + end + state.effects = effects + end + end + }, eventHandlers = { T_Passwall_teleportPlayer = teleportPlayer, } diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index d57658e..b274a74 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -39,11 +39,12 @@ local function parseEffects(values, offset) row.area = nil else row.range = RANGE[row.range] - row.magnitudeMin = row.min - row.magnitudeMax = row.max + row.magnitudeMin = row.min or row.magnitude + row.magnitudeMax = row.max or row.magnitude end row.min = nil row.max = nil + row.magnitude = nil end return implemented, effects end @@ -195,8 +196,11 @@ local function addMiscEffects() onTarget = false, onTouch = false, hasDuration = false, cost = magicData.td_misc_effects.T_mysticism_Passwall[2] * 0.5 -- compensate for MWSE using area instead of magnitude }) - --addMiscEffect('T_mysticism_BanishDae', {}) + --addMiscEffect('T_mysticism_BanishDae', {}) -- Requires a way to trigger death without waiting for the animation addMiscEffect('T_mysticism_ReflectDmg', {}) + --addMiscEffect('T_mysticism_DetHuman', {}) -- Requires map dehardcoding + --addMiscEffect('T_alteration_RadShield', {}) -- Requires a (more elegant) way of applying variable magnitude blind effects + addMiscEffect('T_restoration_FortifyCasting', {}) end return { From 7524d92ca9545b973661339ac5d46d5e13071302 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 25 Apr 2026 12:39:54 +0200 Subject: [PATCH 17/28] Implement Resartus --- .../scripts/TamrielData/global_miscspells.lua | 66 +++++++++++++++++++ Data Files/scripts/TamrielData/load_magic.lua | 2 + 2 files changed, 68 insertions(+) diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index 3f77b93..54965f4 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -48,6 +48,64 @@ local function teleportPlayer(data) world.vfx.spawn(passwall_target_effect_model, data.position) end +local function resartusEquipment(actor, magnitude, type) + if magnitude <= 0 then + return + end + local equipment = actor.type.getEquipment(actor) + local toFix = {} + for slot, item in pairs(equipment) do + if type.objectIsInstance(item) then + local data = types.Item.itemData(item) + local record = item.type.records[item.recordId] + local maxHealth = record.health + local maxCharge = 0 + if record.enchant then + local enchantment = core.magic.enchantments.records[record.enchant] + if enchantment then + -- FIXME: this is incorrect for autocalc + maxCharge = enchantment.charge + end + end + local hasDamage = data.condition and data.condition < maxHealth + local missingCharge = data.enchantmentCharge and data.enchantmentCharge < maxCharge + if hasDamage or missingCharge then + table.insert(toFix, { + data = data, + hasDamage = hasDamage, + missingCharge = missingCharge, + maxHealth = maxHealth, + maxCharge = maxCharge + }) + end + end + end + local healthRemaining = magnitude + local chargeRemaining = magnitude + while healthRemaining > 0 and chargeRemaining > 0 do + local changed = false + for _, data in pairs(toFix) do + if data.hasDamage and healthRemaining > 0 then + local health = math.min(data.data.condition + 1, data.maxHealth) + data.hasDamage = health ~= data.maxHealth + data.data.condition = health + healthRemaining = healthRemaining - 1 + changed = true + end + if data.missingCharge and chargeRemaining > 0 then + local charge = math.min(data.data.enchantmentCharge + 1, data.maxCharge) + data.missingCharge = charge ~= data.maxCharge + data.data.enchantmentCharge = charge + chargeRemaining = chargeRemaining - 1 + changed = true + end + end + if not changed then + break + end + end +end + local function toKey(actor, id, index) return actor.id .. ',' .. id .. ',' .. index end @@ -73,6 +131,14 @@ local onStart = { t_mysticism_reflectdmg = function(target, spell, effect, track) track.ignore = false end, + t_restoration_armorresartus = function(target, spell, effect, track) + resartusEquipment(target, effect.magnitudeThisFrame, types.Armor) + track.ignore = false + end, + t_restoration_weaponresartus = function(target, spell, effect, track) + resartusEquipment(target, effect.magnitudeThisFrame, types.Weapon) + track.ignore = false + end, t_restoration_fortifycasting = function(target, spell, effect, track) local activeEffects = target.type.activeEffects(target) activeEffects:modify(-effect.magnitudeThisFrame, 'sound') diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index b274a74..52715ce 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -200,6 +200,8 @@ local function addMiscEffects() addMiscEffect('T_mysticism_ReflectDmg', {}) --addMiscEffect('T_mysticism_DetHuman', {}) -- Requires map dehardcoding --addMiscEffect('T_alteration_RadShield', {}) -- Requires a (more elegant) way of applying variable magnitude blind effects + addMiscEffect('T_restoration_ArmorResartus', { hasDuration = false }) + addMiscEffect('T_restoration_WeaponResartus', { hasDuration = false }) addMiscEffect('T_restoration_FortifyCasting', {}) end From fdc993a9a2f1b8269696491caf7f37b668f9e7f3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 25 Apr 2026 16:16:20 +0200 Subject: [PATCH 18/28] Adapt hyacinth's Distract --- Data Files/MWSE/mods/TamrielData/magic.lua | 40 +---- .../MWSE/mods/TamrielData/magicdata.lua | 39 +++++ .../scripts/TamrielData/actor_magic.lua | 138 ++++++++++++++++++ .../scripts/TamrielData/global_miscspells.lua | 45 ++++++ Data Files/scripts/TamrielData/load_magic.lua | 2 + 5 files changed, 225 insertions(+), 39 deletions(-) diff --git a/Data Files/MWSE/mods/TamrielData/magic.lua b/Data Files/MWSE/mods/TamrielData/magic.lua index 06fa094..94c6324 100644 --- a/Data Files/MWSE/mods/TamrielData/magic.lua +++ b/Data Files/MWSE/mods/TamrielData/magic.lua @@ -110,44 +110,6 @@ end local magicData = require("TamrielData.magicdata") --- race id, isFemale, distraction voice files, distraction end voice lines -local distractedVoiceLines = { - { "Argonian", false, { "vo\\a\\m\\Idl_AM001.mp3", "vo\\a\\m\\Hlo_AM056.mp3" }, { "vo\\a\\m\\Idl_AM008.mp3" } }, - { "Argonian", true, { "vo\\a\\f\\Idl_AF007.mp3", "vo\\a\\f\\Idl_AF004.mp3" }, { "vo\\a\\f\\Idl_AF002.mp3" } }, - { "Breton", false, { }, { } }, - { "Breton", true, { "vo\\b\\f\\Idl_BF001.mp3", "vo\\b\\f\\Idl_BF005.mp3" }, { "vo\\b\\f\\Idl_BF003.mp3" } }, - { "Dark Elf", false, { "vo\\d\\m\\Idl_DM006.mp3", "vo\\d\\m\\Idl_DM007.mp3" }, { "vo\\d\\m\\Idl_DM008.mp3" } }, - { "Dark Elf", true, { "vo\\d\\f\\Idl_DF006.mp3" }, { "vo\\d\\f\\Idl_DF003.mp3" } }, - { "High Elf", false, { "vo\\h\\m\\Hlo_HM056.mp3" }, { "vo\\i\\m\\Idl_HF007.mp3" } }, - { "High Elf", true, { "vo\\h\\f\\Hlo_HF056.mp3" }, { "vo\\i\\f\\Idl_HF007.mp3" } }, - { "Imperial", false, { "vo\\i\\m\\Idl_IM008.mp3" }, { "vo\\i\\m\\Idl_IM005.mp3" } }, - { "Imperial", true, { "vo\\i\\f\\Idl_IF001.mp3" }, { "vo\\i\\f\\Idl_IF009.mp3" } }, - { "Khajiit", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "Khajiit", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "T_Els_Cathay", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "T_Els_Cathay-raht", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "T_Els_Dagi-raht", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "T_Els_Ohmes", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "T_Els_Ohmes-raht", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "T_Els_Suthay", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "T_Els_Tojay", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, - { "T_Els_Cathay", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "T_Els_Cathay-raht", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "T_Els_Dagi-raht", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "T_Els_Ohmes", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "T_Els_Ohmes-raht", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "T_Els_Suthay", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "T_Els_Tojay", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, - { "Nord", false, { "vo\\n\\m\\Idl_NM001.mp3" }, { "vo\\n\\m\\Idl_NM009.mp3" } }, - { "Nord", true, { "vo\\n\\f\\Idl_NF002.mp3", "vo\\n\\f\\Idl_NF004.mp3" }, { "vo\\n\\f\\Idl_NM008.mp3" } }, - { "Orc", false, { "vo\\o\\m\\Idl_OM001.mp3", "vo\\o\\m\\Idl_OM002.mp3" }, { "vo\\o\\m\\Idl_OM004.mp3", "vo\\o\\m\\Idl_OM009.mp3" } }, - { "Orc", true, { "vo\\o\\f\\Idl_OF009.mp3" }, { } }, - { "Redguard", false, { }, { } }, - { "Redguard", true, { "vo\\r\\f\\Idl_RF002.mp3", "vo\\r\\f\\Idl_RF008.mp3" }, { "vo\\r\\f\\Idl_RF003.mp3", "vo\\r\\f\\Idl_RF007.mp3" } }, - { "Wood Elf", false, { "vo\\w\\m\\Idl_WM009.mp3" }, { "vo\\w\\m\\Idl_WM006.mp3", "vo\\w\\m\\Idl_WM007.mp3" } }, - { "Wood Elf", true, { "vo\\w\\f\\Idl_WF006.mp3", "vo\\w\\f\\Idl_WF009.mp3" }, { "vo\\w\\f\\Idl_WF003.mp3", "vo\\w\\f\\Idl_WF007.mp3" } }, -} - local prismaticReferences = {} local distractedReferences = {} -- Should probably decide on a consistent naming scheme for tables @@ -1474,7 +1436,7 @@ end ---@param isEnd boolean local function playDistractedVoiceLine(ref, isEnd) if ref.mobile.actorType == tes3.actorType.npc and not ref.mobile.hasVampirism then - for _,v in pairs(distractedVoiceLines) do + for _,v in pairs(magicData.distractedVoiceLines) do local raceID, isFemale, voicesStart, voicesEnd = unpack(v) if ref.baseObject.race.id == raceID and ref.baseObject.female == isFemale then local voices diff --git a/Data Files/MWSE/mods/TamrielData/magicdata.lua b/Data Files/MWSE/mods/TamrielData/magicdata.lua index ab45908..655bc80 100644 --- a/Data Files/MWSE/mods/TamrielData/magicdata.lua +++ b/Data Files/MWSE/mods/TamrielData/magicdata.lua @@ -610,6 +610,44 @@ local td_ingredients = { { id = "T_mysticism_Insight" } }, } +-- race id, isFemale, distraction voice files, distraction end voice lines +local distractedVoiceLines = { + { "Argonian", false, { "vo\\a\\m\\Idl_AM001.mp3", "vo\\a\\m\\Hlo_AM056.mp3" }, { "vo\\a\\m\\Idl_AM008.mp3" } }, + { "Argonian", true, { "vo\\a\\f\\Idl_AF007.mp3", "vo\\a\\f\\Idl_AF004.mp3" }, { "vo\\a\\f\\Idl_AF002.mp3" } }, + { "Breton", false, { }, { } }, + { "Breton", true, { "vo\\b\\f\\Idl_BF001.mp3", "vo\\b\\f\\Idl_BF005.mp3" }, { "vo\\b\\f\\Idl_BF003.mp3" } }, + { "Dark Elf", false, { "vo\\d\\m\\Idl_DM006.mp3", "vo\\d\\m\\Idl_DM007.mp3" }, { "vo\\d\\m\\Idl_DM008.mp3" } }, + { "Dark Elf", true, { "vo\\d\\f\\Idl_DF006.mp3" }, { "vo\\d\\f\\Idl_DF003.mp3" } }, + { "High Elf", false, { "vo\\h\\m\\Hlo_HM056.mp3" }, { "vo\\i\\m\\Idl_HF007.mp3" } }, + { "High Elf", true, { "vo\\h\\f\\Hlo_HF056.mp3" }, { "vo\\i\\f\\Idl_HF007.mp3" } }, + { "Imperial", false, { "vo\\i\\m\\Idl_IM008.mp3" }, { "vo\\i\\m\\Idl_IM005.mp3" } }, + { "Imperial", true, { "vo\\i\\f\\Idl_IF001.mp3" }, { "vo\\i\\f\\Idl_IF009.mp3" } }, + { "Khajiit", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "Khajiit", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "T_Els_Cathay", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "T_Els_Cathay-raht", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "T_Els_Dagi-raht", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "T_Els_Ohmes", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "T_Els_Ohmes-raht", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "T_Els_Suthay", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "T_Els_Tojay", false, { "vo\\k\\m\\Idl_KM005.mp3", "vo\\k\\m\\Idl_KM006.mp3", "vo\\k\\m\\Idl_KM007.mp3" }, { "vo\\k\\m\\Idl_KM002.mp3", "vo\\k\\m\\Idl_KM003.mp3" } }, + { "T_Els_Cathay", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "T_Els_Cathay-raht", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "T_Els_Dagi-raht", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "T_Els_Ohmes", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "T_Els_Ohmes-raht", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "T_Els_Suthay", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "T_Els_Tojay", true, { "vo\\k\\f\\Idl_KF005.mp3", "vo\\k\\f\\Idl_KF006.mp3", "vo\\k\\f\\Idl_KF007.mp3" }, { "vo\\k\\f\\Idl_KF002.mp3", "vo\\k\\f\\Idl_KF003.mp3" } }, + { "Nord", false, { "vo\\n\\m\\Idl_NM001.mp3" }, { "vo\\n\\m\\Idl_NM009.mp3" } }, + { "Nord", true, { "vo\\n\\f\\Idl_NF002.mp3", "vo\\n\\f\\Idl_NF004.mp3" }, { "vo\\n\\f\\Idl_NM008.mp3" } }, + { "Orc", false, { "vo\\o\\m\\Idl_OM001.mp3", "vo\\o\\m\\Idl_OM002.mp3" }, { "vo\\o\\m\\Idl_OM004.mp3", "vo\\o\\m\\Idl_OM009.mp3" } }, + { "Orc", true, { "vo\\o\\f\\Idl_OF009.mp3" }, { } }, + { "Redguard", false, { }, { } }, + { "Redguard", true, { "vo\\r\\f\\Idl_RF002.mp3", "vo\\r\\f\\Idl_RF008.mp3" }, { "vo\\r\\f\\Idl_RF003.mp3", "vo\\r\\f\\Idl_RF007.mp3" } }, + { "Wood Elf", false, { "vo\\w\\m\\Idl_WM009.mp3" }, { "vo\\w\\m\\Idl_WM006.mp3", "vo\\w\\m\\Idl_WM007.mp3" } }, + { "Wood Elf", true, { "vo\\w\\f\\Idl_WF006.mp3", "vo\\w\\f\\Idl_WF009.mp3" }, { "vo\\w\\f\\Idl_WF003.mp3", "vo\\w\\f\\Idl_WF007.mp3" } }, +} + return { td_summon_effects = td_summon_effects, td_bound_effects = td_bound_effects, @@ -623,4 +661,5 @@ return { td_potions = td_potions, td_enchanted_items = td_enchanted_items, td_ingredients = td_ingredients, + distractedVoiceLines = distractedVoiceLines, } diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 78810fd..88fffff 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -1,8 +1,12 @@ local core = require('openmw.core') local I = require('openmw.interfaces') +local nearby = require('openmw.nearby') local self = require('openmw.self') +local types = require('openmw.types') local auxUtil = require('openmw_aux.util') +local FT_TO_UNITS = 22.1 + local activeEffects = self.type.activeEffects(self) local activeSpells = self.type.activeSpells(self) @@ -64,10 +68,144 @@ I.Combat.addOnHitHandler(function(attack) attack.attacker:sendEvent('Hit', reflectedAttack) end) +local function getDistractDestination(caster, range) + local casterPos = caster and caster.position + local selfPos = self.position + local agentBounds = self.type.getPathfindingAgentBounds(self) + + local function getCasterPenalty(candidate) + if not casterPos then + return 0 + end + local status, path = nearby.findPath(selfPos, candidate, { agentBounds = agentBounds }) + local penalty = (candidate - casterPos):length() * 0.25 + if status == nearby.FIND_PATH_STATUS.Success and next(path) then + local min = math.huge + for _, point in pairs(path) do + local distance = (point - casterPos):length2() + min = math.min(min, distance) + end + penalty = penalty + min + end + return penalty + end + + local bestPos = nil + local bestScore = 0 + local SAMPLES = 12 + + for i = 1, SAMPLES do + local candidate = nearby.findRandomPointAroundCircle(selfPos, range, { agentBounds = agentBounds }) + if candidate and math.abs(candidate.z - selfPos.z) < 384 then + local score = getCasterPenalty(candidate) + (candidate - selfPos):length() * 0.5 + if score > bestScore then + bestScore = score + bestPos = candidate + end + end + end + return bestPos +end + +function playDistractedVoiceLine(isEnd) + if types.NPC.objectIsInstance(self) and not self.type.isDead(self) and not self.type.isWerewolf(self) and activeEffects:getEffect('Vampirism').magnitude <= 0 then + -- Handling this in a global script so we only need one instance of the voice lines table in memory + core.sendGlobalEvent('T_DistractVoice', { actor = self.object, isEnd = isEnd }) + end +end + +local state = {} + +local timer = 0 + return { engineHandlers = { onInactive = function() core.sendGlobalEvent('T_ActorInactive', self.object) + end, + onSave = function() + return state + end, + onLoad = function(data) + if data then + state = data + end + end, + onUpdate = function(dt) + if not state.distract or not state.distract.returning then + return + end + timer = timer + dt + if timer >= 1 then + timer = 0 + local active = I.AI.getActivePackage() + if not active or active.type ~= 'Travel' then + self.type.stats.ai.hello(self).base = state.distract.hello + local resetRotation = true + if state.distract.wander then + resetRotation = state.distract.wander.distance == 0 + I.AI.startPackage(state.distract.wander) + end + if resetRotation then + local yaw = self.rotation:getYaw() + self.controls.yawChange = state.distract.originYaw - yaw + end + state.distract = nil + end + end + end + }, + eventHandlers = { + Died = function() + state.distract = nil + end, + T_Distract = function(data) + local active = I.AI.getActivePackage() + if active and active.type ~= 'Wander' then + return + end + local destination = getDistractDestination(data.caster, data.magnitude * FT_TO_UNITS) + if destination then + if not state.distract then + local hello = self.type.stats.ai.hello(self) + state.distract = { + hello = hello.base, + origin = self.position, + originYaw = self.rotation:getYaw(), + worldSpace = self.cell.worldSpaceId + } + if active then + state.distract.wander = { + type = 'Wander', + distance = active.distance, + duration = active.duration, + idle = active.idle and auxUtil.shallowCopy(active.idle), + isRepeat = active.isRepeat + } + end + hello.base = 0 + end + state.distract.returning = false + if math.random() < 0.45 then + playDistractedVoiceLine(false) + end + I.AI.startPackage({ type = 'Travel', destPosition = destination, cancelOther = true, isRepeat = false }) + end + end, + T_DistractFinished = function(effect) + if not state.distract then + return + end + if activeEffects:getEffect(effect).magnitude <= 0 then + state.distract.returning = true + if math.random() < 0.45 then + playDistractedVoiceLine(true) + end + if self.cell and self.cell.worldSpaceId == state.distract.worldSpace then + timer = 0 + I.AI.startPackage({ type = 'Travel', destPosition = state.distract.origin, cancelOther = true, isRepeat = false }) + end + end end } } diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index 54965f4..83f671d 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -8,8 +8,16 @@ end local I = require('openmw.interfaces') local types = require('openmw.types') local world = require('openmw.world') +local magicData = require('MWSE.mods.TamrielData.magicdata') local passwall_target_effect_model = types.Static.records[passwallEffect.hitStatic].model +local distractedVoices = {} +for _, line in pairs(magicData.distractedVoiceLines) do + local raceID, isFemale, voicesStart, voicesEnd = unpack(line) + raceID = raceID:lower() + distractedVoices[raceID] = distractedVoices[raceID] or {} + distractedVoices[raceID][isFemale and "female" or "male"] = { voicesStart, voicesEnd } +end local function triggerCrimeIfTrespassing(data) if not data.targetObject or not data.targetObject.owner or not types.Lockable.isLocked(data.targetObject) then @@ -106,6 +114,22 @@ local function resartusEquipment(actor, magnitude, type) end end +local function playDistractedVoiceLine(data) + local record = data.actor.type.records[data.actor.recordId] + local race = distractedVoices[record.race] + if not race then + return + end + local lines = race[record.isMale and "male" or "female"] + if lines then + local files = data.isEnd and lines[2] or lines[1] + local path = files and files[math.random(#files)] + if path then + core.sound.say('sound/' .. path, data.actor) + end + end +end + local function toKey(actor, id, index) return actor.id .. ',' .. id .. ',' .. index end @@ -121,6 +145,18 @@ local function store(target, spell, effect) state.effects[key] = { id = id, index = index, actor = target, magnitude = effect.magnitudeThisFrame, effect = effect.id } end +local function distract(type) + return function(target, spell, effect, track) + if type.objectIsInstance(target) and not types.Player.objectIsInstance(target) then + target:sendEvent('T_Distract', { magnitude = effect.magnitudeThisFrame, caster = spell.caster }) + store(target, spell, effect) + track.ignore = false + else + target.type.activeEffects(target):remove(effect.id) + end + end +end + local onStart = { t_mysticism_passwall = function(target, spell, effect, track) if types.Player.objectIsInstance(target) then @@ -139,6 +175,8 @@ local onStart = { resartusEquipment(target, effect.magnitudeThisFrame, types.Weapon) track.ignore = false end, + t_illusion_distractcreature = distract(types.Creature), + t_illusion_distracthumanoid = distract(types.NPC), t_restoration_fortifycasting = function(target, spell, effect, track) local activeEffects = target.type.activeEffects(target) activeEffects:modify(-effect.magnitudeThisFrame, 'sound') @@ -151,6 +189,12 @@ local onEnd = { t_restoration_fortifycasting = function(effect) local activeEffects = effect.actor.type.activeEffects(effect.actor) activeEffects:modify(effect.magnitude, 'sound') + end, + t_illusion_distractcreature = function(effect) + effect.actor:sendEvent('T_DistractFinished', effect.effect) + end, + t_illusion_distracthumanoid = function(effect) + effect.actor:sendEvent('T_DistractFinished', effect.effect) end } @@ -194,5 +238,6 @@ return { }, eventHandlers = { T_Passwall_teleportPlayer = teleportPlayer, + T_DistractVoice = playDistractedVoiceLine, } } diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 52715ce..8ff70e8 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -202,6 +202,8 @@ local function addMiscEffects() --addMiscEffect('T_alteration_RadShield', {}) -- Requires a (more elegant) way of applying variable magnitude blind effects addMiscEffect('T_restoration_ArmorResartus', { hasDuration = false }) addMiscEffect('T_restoration_WeaponResartus', { hasDuration = false }) + addMiscEffect('T_illusion_DistractCreature', { unreflectable = true, onSelf = false, harmful = false }) + addMiscEffect('T_illusion_DistractHumanoid', { unreflectable = true, onSelf = false, harmful = false }) addMiscEffect('T_restoration_FortifyCasting', {}) end From 8feed060439f515ea0d28bc9573946e8eb685b43 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 26 Apr 2026 09:07:24 +0200 Subject: [PATCH 19/28] Implement Summon Bouquet --- Data Files/MWSE/mods/TamrielData/magic.lua | 20 +----------------- .../MWSE/mods/TamrielData/magicdata.lua | 21 ++++++++++++++++++- .../scripts/TamrielData/actor_summons.lua | 2 +- .../scripts/TamrielData/global_summons.lua | 11 ++++++++-- Data Files/scripts/TamrielData/load_magic.lua | 1 + 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Data Files/MWSE/mods/TamrielData/magic.lua b/Data Files/MWSE/mods/TamrielData/magic.lua index 94c6324..052727f 100644 --- a/Data Files/MWSE/mods/TamrielData/magic.lua +++ b/Data Files/MWSE/mods/TamrielData/magic.lua @@ -225,24 +225,6 @@ local wabbajackCreatures = { "golden saint" } -local sanguineRoseDaedra = { - "dremora_summon", - "T_Dae_Cre_Seduc_01", - "atronach_flame", - "atronach_frost", - "atronach_storm", - "scamp_summon", - "T_Dae_Cre_Verm_01", - "clannfear_summon", - "T_Dae_Cre_Herne_01", - "T_Dae_Cre_Morphoid_01", - "T_Dae_Cre_SpiderDae_01", - "hunger_summon", - "winged twilight_summon", - "golden saint_summon", - "daedroth_summon", -} - -- actor id local gazeOfVelothImmuneActors = { ["vivec_god"] = true, @@ -3633,7 +3615,7 @@ event.register(tes3.event.magicEffectsResolved, function() targetsAttributes = false, targetsSkills = false, onTick = function(eventData) - eventData:triggerSummon(sanguineRoseDaedra[math.random(#sanguineRoseDaedra)]) + eventData:triggerSummon(magicData.sanguineRoseDaedra[math.random(#magicData.sanguineRoseDaedra)]) end, onCollision = nil }) diff --git a/Data Files/MWSE/mods/TamrielData/magicdata.lua b/Data Files/MWSE/mods/TamrielData/magicdata.lua index 655bc80..4cdf460 100644 --- a/Data Files/MWSE/mods/TamrielData/magicdata.lua +++ b/Data Files/MWSE/mods/TamrielData/magicdata.lua @@ -75,7 +75,7 @@ local td_misc_effects = { T_restoration_FortifyCasting = { "miscFortifyCasting", 1, "td\\s\\td_s_ftfy_cast.tga", "miscFortifyCastingDesc", "fortifyAttack" }, T_illusion_PrismaticLight = { "miscPrismaticLight", 0.4, "td\\s\\td_s_p_light.tga", "miscPrismaticLightDesc", "light" }, T_mysticism_BloodMagic = { "miscBloodMagic", 2, "td\\s\\td_s_blood_magic.tga", "miscBloodMagicDesc", "detectAnimal" }, - T_conjuration_SanguineRose = { "miscSanguineRose", 40, "td\\s\\td_s_sanguine.dds.tga", "miscSanguineRoseDesc", "summonDremora" }, + T_conjuration_SanguineRose = { "miscSanguineRose", 40, "td\\s\\td_s_sanguine.tga", "miscSanguineRoseDesc", "summonDremora" }, T_mysticism_DetValuables = { "miscDetectValuables", 1.5, "td\\s\\td_s_det_value.tga", "miscDetectValuablesDesc", "detectAnimal" }, T_mysticism_MagickaWard = { "miscMagickaWard", 20, "td\\s\\td_s_magickaward.tga", "miscMagickaWardDesc", "detectAnimal" }, T_illusion_Ethereal = { "miscEthereal", 40, "td\\s\\td_s_ethereal.tga", "miscEtherealtDesc", "blind" }, @@ -648,6 +648,24 @@ local distractedVoiceLines = { { "Wood Elf", true, { "vo\\w\\f\\Idl_WF006.mp3", "vo\\w\\f\\Idl_WF009.mp3" }, { "vo\\w\\f\\Idl_WF003.mp3", "vo\\w\\f\\Idl_WF007.mp3" } }, } +local sanguineRoseDaedra = { + "dremora_summon", + "T_Dae_Cre_Seduc_01", + "atronach_flame", + "atronach_frost", + "atronach_storm", + "scamp_summon", + "T_Dae_Cre_Verm_01", + "clannfear_summon", + "T_Dae_Cre_Herne_01", + "T_Dae_Cre_Morphoid_01", + "T_Dae_Cre_SpiderDae_01", + "hunger_summon", + "winged twilight_summon", + "golden saint_summon", + "daedroth_summon", +} + return { td_summon_effects = td_summon_effects, td_bound_effects = td_bound_effects, @@ -662,4 +680,5 @@ return { td_enchanted_items = td_enchanted_items, td_ingredients = td_ingredients, distractedVoiceLines = distractedVoiceLines, + sanguineRoseDaedra = sanguineRoseDaedra, } diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index 9bb6a35..b5e2ca7 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -1,6 +1,6 @@ local core = require('openmw.core') -if not core.magic.effects.records['T_summon_Devourer'] then +if not core.magic.effects.records['T_summon_Devourer'] and not core.magic.effects.records['T_conjuration_SanguineRose'] then return end diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index 3f01b65..92c71f9 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -1,6 +1,6 @@ local core = require('openmw.core') -if not core.magic.effects.records['T_summon_Devourer'] then +if not core.magic.effects.records['T_summon_Devourer'] and not core.magic.effects.records['T_conjuration_SanguineRose'] then return end @@ -24,8 +24,15 @@ local function toKey(actor, id, index) return actor.id .. ',' .. id .. ',' .. index end +local function getSummon(effectId) + if effectId == 't_conjuration_sanguinerose' then + return magicData.sanguineRoseDaedra[math.random(#magicData.sanguineRoseDaedra)] + end + return summons[effectId] +end + I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) - local creature = summons[effect.id] + local creature = getSummon(effect.id) if not creature then return end diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 8ff70e8..354daf7 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -205,6 +205,7 @@ local function addMiscEffects() addMiscEffect('T_illusion_DistractCreature', { unreflectable = true, onSelf = false, harmful = false }) addMiscEffect('T_illusion_DistractHumanoid', { unreflectable = true, onSelf = false, harmful = false }) addMiscEffect('T_restoration_FortifyCasting', {}) + addMiscEffect('T_conjuration_SanguineRose', { allowsSpellmaking = false, allowsEnchanting = false }) end return { From b35cd2037d0bedc6c975d1194c71cebeea5a82cf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 26 Apr 2026 11:37:54 +0200 Subject: [PATCH 20/28] Implement Corruption --- Data Files/MWSE/mods/TamrielData/magic.lua | 44 +-------------- .../MWSE/mods/TamrielData/magicdata.lua | 43 ++++++++++++++ Data Files/l10n/TamrielData/en.yaml | 2 + Data Files/l10n/TamrielData/fr.yaml | 2 + .../scripts/TamrielData/actor_summons.lua | 5 ++ .../scripts/TamrielData/global_miscspells.lua | 56 +++++++++++++++++++ .../scripts/TamrielData/global_summons.lua | 52 +++++++++++++++-- Data Files/scripts/TamrielData/load_magic.lua | 2 + 8 files changed, 158 insertions(+), 48 deletions(-) diff --git a/Data Files/MWSE/mods/TamrielData/magic.lua b/Data Files/MWSE/mods/TamrielData/magic.lua index 052727f..24982f0 100644 --- a/Data Files/MWSE/mods/TamrielData/magic.lua +++ b/Data Files/MWSE/mods/TamrielData/magic.lua @@ -235,48 +235,6 @@ local gazeOfVelothImmuneActors = { ["Sky_qRe_KWMG6_Azra"] = true, } --- script id -local safeScripts = { - ["nolore"] = true, - ["slaveScript"] = true, - ["fortloreboozeScript"] = true, - ["TR_m3_Kha_Methats_sc"] = true, - ["TR_m3_NPC_OE_commonNoLore"] = true, - ["TR_m3_NPC_OE_poorNoLore"] = true, - ["TR_m3_NPC_OE_richNoLore"] = true, - ["TR_m3_NPC_OE_towerNoLore"] = true, - ["TR_m4_AA_Vf_NPC_NoLoresc"] = true, - ["TR_m7_NPC_RuddyEggsNoLore"] = true, - ["TR_m7_Ns_KhanVolnyr_sc"] = true, - ["TR_m3_q_kharg"] = true, - ["TR_m3_Kha_AtroLordDis_sc"] = true, - ["TR_m3_Kha_Black_Heart_Script"] = true, - ["TR_m3_Kha_ClannLordDis_sc"] = true, - ["TR_m3_Kha_FireScamp_sc"] = true, - ["TR_m1_Lornie_Slave_scpt"] = true, - ["TR_m4_OranSlave_Sc"] = true, - ["TR_m1_T_CouncilorSc"] = true, - ["TR_m2_T_CouncilorSc"] = true, - ["TR_NecMQ_MovingNPCScript"] = true, - ["TR_m1_FW_TG3_HrongalDrink"] = true, - ["TR_m1_MinTalScript"] = true, - ["TR_m1_NPC_DiceGambler"] = true, - ["TR_m2_NPC_DiceGambler"] = true, - ["TR_m3_NPC_DiceGambler"] = true, - ["TR_m3_NPC_DiceGamblerNoLore"] = true, - ["TR_m3_NPC_DiceGamblerOECom"] = true, - ["TR_m3_NPC_DiceGamblerOEPoor"] = true, - ["TR_m4_NPC_DiceGambler"] = true, - ["TR_m5_NPC_DiceGambler"] = true, - ["TR_m6_NPC_DiceGambler"] = true, - ["TR_m7_NPC_DiceGambler"] = true, - ["TR_m1_NPC_Fervas_Shulisa"] = true, - ["TR_m1_NPC_Gilen_Indothan"] = true, - ["TR_m1_NPC_Malvas_Relvani"] = true, - ["TR_m1_T_Seducer"] = true, - ["TR_m3_q_vampambush"] = true, -} - ---@param table table function this.replaceSpells(table) for _,v in pairs(table) do @@ -1707,7 +1665,7 @@ local function corruptionEffect(e) local target = e.effectInstance.target if target.id ~= tes3.player.data.tamrielData.corruptionReferenceID then -- Memory errors can be reported if the effect is applied to the summon and doing so is weird anyways - if target.baseObject.script and (not ((target.baseObject.script.id:find("T_ScNpc") and not target.baseObject.script.id:find("_Were")) or safeScripts[target.baseObject.script.id]) or hasScriptedItem(target.mobile.inventory)) then -- Checks whether the target has a scripted item or a script that is not known to be safely cloneable + if target.baseObject.script and (not ((target.baseObject.script.id:find("T_ScNpc") and not target.baseObject.script.id:find("_Were")) or magicData.safeScripts[target.baseObject.script.id]) or hasScriptedItem(target.mobile.inventory)) then -- Checks whether the target has a scripted item or a script that is not known to be safely cloneable tes3ui.showNotifyMenu(common.i18n("magic.corruptionScript", { target.object.name })) e.effectInstance.state = tes3.spellState.retired restoreCharge(e.sourceInstance) diff --git a/Data Files/MWSE/mods/TamrielData/magicdata.lua b/Data Files/MWSE/mods/TamrielData/magicdata.lua index 4cdf460..bfd3a24 100644 --- a/Data Files/MWSE/mods/TamrielData/magicdata.lua +++ b/Data Files/MWSE/mods/TamrielData/magicdata.lua @@ -666,6 +666,48 @@ local sanguineRoseDaedra = { "daedroth_summon", } +-- script id +local safeScripts = { + ["nolore"] = true, + ["slaveScript"] = true, + ["fortloreboozeScript"] = true, + ["TR_m3_Kha_Methats_sc"] = true, + ["TR_m3_NPC_OE_commonNoLore"] = true, + ["TR_m3_NPC_OE_poorNoLore"] = true, + ["TR_m3_NPC_OE_richNoLore"] = true, + ["TR_m3_NPC_OE_towerNoLore"] = true, + ["TR_m4_AA_Vf_NPC_NoLoresc"] = true, + ["TR_m7_NPC_RuddyEggsNoLore"] = true, + ["TR_m7_Ns_KhanVolnyr_sc"] = true, + ["TR_m3_q_kharg"] = true, + ["TR_m3_Kha_AtroLordDis_sc"] = true, + ["TR_m3_Kha_Black_Heart_Script"] = true, + ["TR_m3_Kha_ClannLordDis_sc"] = true, + ["TR_m3_Kha_FireScamp_sc"] = true, + ["TR_m1_Lornie_Slave_scpt"] = true, + ["TR_m4_OranSlave_Sc"] = true, + ["TR_m1_T_CouncilorSc"] = true, + ["TR_m2_T_CouncilorSc"] = true, + ["TR_NecMQ_MovingNPCScript"] = true, + ["TR_m1_FW_TG3_HrongalDrink"] = true, + ["TR_m1_MinTalScript"] = true, + ["TR_m1_NPC_DiceGambler"] = true, + ["TR_m2_NPC_DiceGambler"] = true, + ["TR_m3_NPC_DiceGambler"] = true, + ["TR_m3_NPC_DiceGamblerNoLore"] = true, + ["TR_m3_NPC_DiceGamblerOECom"] = true, + ["TR_m3_NPC_DiceGamblerOEPoor"] = true, + ["TR_m4_NPC_DiceGambler"] = true, + ["TR_m5_NPC_DiceGambler"] = true, + ["TR_m6_NPC_DiceGambler"] = true, + ["TR_m7_NPC_DiceGambler"] = true, + ["TR_m1_NPC_Fervas_Shulisa"] = true, + ["TR_m1_NPC_Gilen_Indothan"] = true, + ["TR_m1_NPC_Malvas_Relvani"] = true, + ["TR_m1_T_Seducer"] = true, + ["TR_m3_q_vampambush"] = true, +} + return { td_summon_effects = td_summon_effects, td_bound_effects = td_bound_effects, @@ -681,4 +723,5 @@ return { td_ingredients = td_ingredients, distractedVoiceLines = distractedVoiceLines, sanguineRoseDaedra = sanguineRoseDaedra, + safeScripts = safeScripts, } diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 31d5639..e9a6d78 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -24,6 +24,8 @@ TamrielData_magic_passwallAlpha: "You cannot pass through that." TamrielData_magic_passwallExterior: "You must be in a confined space." TamrielData_magic_passwallDoorExterior: "You cannot leave a confined space." TamrielData_magic_passwallUnderwater: "You cannot be underwater." +Magic_corruptionScript: "{target} cannot be corrupted." +Magic_corruptionSummon: "You cannot corrupt a being summoned by the Skull of Corruption." Magic_summonDevourer: "Summon Devourer" Magic_summonDevourerDesc: "This effect summons a devourer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index e9fd2ae..2980e3b 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -24,6 +24,8 @@ TamrielData_magic_passwallAlpha: "Vous ne pouvez pas passer à travers ceci." TamrielData_magic_passwallExterior: "Vous devez vous trouver dans un espace confiné." TamrielData_magic_passwallDoorExterior: "Vous ne pouvez pas quitter un espace confiné." TamrielData_magic_passwallUnderwater: "Vous ne pouvez pas vous trouver sous l'eau." +Magic_corruptionScript: "{target} ne peut être corrompu." +Magic_corruptionSummon: "Vous ne pouvez corrompre un être invoqué par le Crâne de Corruption." Magic_summonDevourer: "Appel de consummeur" Magic_summonDevourerDesc: "Cet effet permet d'invoquer un consummeur des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." diff --git a/Data Files/scripts/TamrielData/actor_summons.lua b/Data Files/scripts/TamrielData/actor_summons.lua index b5e2ca7..30f1b83 100644 --- a/Data Files/scripts/TamrielData/actor_summons.lua +++ b/Data Files/scripts/TamrielData/actor_summons.lua @@ -51,6 +51,11 @@ return { state.id = data.id state.index = data.index self.type.stats.ai.fight(self).base = 30 -- we should probably be using dedicated creature variants + if data.tag == 't_conjuration_corruptionsummon' then + self.type.stats.ai.alarm(self).base = 0 + self.type.stats.ai.flee(self).base = 0 + self.type.stats.ai.hello(self).base = 0 + end end, Died = function() if state.caster ~= nil then diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index 83f671d..672cc28 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -8,6 +8,7 @@ end local I = require('openmw.interfaces') local types = require('openmw.types') local world = require('openmw.world') +local l10n = core.l10n('TamrielData') local magicData = require('MWSE.mods.TamrielData.magicdata') local passwall_target_effect_model = types.Static.records[passwallEffect.hitStatic].model @@ -18,6 +19,10 @@ for _, line in pairs(magicData.distractedVoiceLines) do distractedVoices[raceID] = distractedVoices[raceID] or {} distractedVoices[raceID][isFemale and "female" or "male"] = { voicesStart, voicesEnd } end +local safeScripts = {} +for k, v in pairs(magicData.safeScripts) do + safeScripts[k:lower()] = v +end local function triggerCrimeIfTrespassing(data) if not data.targetObject or not data.targetObject.owner or not types.Lockable.isLocked(data.targetObject) then @@ -130,6 +135,34 @@ local function playDistractedVoiceLine(data) end end +local function canBeCorrupted(target) + if types.Player.objectIsInstance(target) then + return false + end + local record = target.type.records[target.recordId] + local script = record.mwscript + if script and not (script:find("t_scnpc") and not script:find("_were") or safeScripts[script]) then + return false + end + for _, item in pairs(target.type.inventory(target):getAll()) do + if item.type.records[item.recordId].mwscript then + return false + end + end + return true +end + +local function restoreCharge(item, caster) + --TODO !3029 + if not item or not I.SpellCasting then + return + end + local charge = I.SpellCasting.getCostCharge(item, caster) + local data = types.Item.itemData(item) + --TODO cap + data.enchantmentCharge = data.enchantmentCharge + charge +end + local function toKey(actor, id, index) return actor.id .. ',' .. id .. ',' .. index end @@ -175,6 +208,29 @@ local onStart = { resartusEquipment(target, effect.magnitudeThisFrame, types.Weapon) track.ignore = false end, + t_conjuration_corruption = function(target, spell, effect, track) + if not spell.caster or not spell.caster:isValid() then + return + end + if I.T_SummonMagic.isCorruptionSummon(target) then + if types.Player.objectIsInstance(spell.caster) then + spell.caster:sendEvent('ShowMessage', { message = l10n('Magic_corruptionSummon') }) + end + target.type.activeEffects(target):remove(effect.id) + restoreCharge(spell.item, spell.caster) + elseif canBeCorrupted(target) then + I.T_SummonMagic.setCorruptedId(spell.caster, target.recordId) + track.ignore = false + types.Actor.activeSpells(spell.caster):add({ id = 'T_Dae_Cnj_UNI_CorruptionSummon', effects = { 0 }, ignoreResistances = true, ignoreSpellAbsorption = true, ignoreReflect = true, caster = spell.caster }) + else + if types.Player.objectIsInstance(spell.caster) then + local record = target.type.records[target.recordId] + spell.caster:sendEvent('ShowMessage', { message = l10n('Magic_corruptionScript', { target = record.name or record.id }) }) + end + target.type.activeEffects(target):remove(effect.id) + restoreCharge(spell.item, spell.caster) + end + end, t_illusion_distractcreature = distract(types.Creature), t_illusion_distracthumanoid = distract(types.NPC), t_restoration_fortifycasting = function(target, spell, effect, track) diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index 92c71f9..30d7d85 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -17,35 +17,41 @@ local startVfx = types.Static.records['VFX_Summon_Start'].model local endVfx = types.Static.records['VFX_Summon_End'].model local state = { - summons = {} + summons = {}, + corruption = {}, + corruptionSummons = {} } local function toKey(actor, id, index) return actor.id .. ',' .. id .. ',' .. index end -local function getSummon(effectId) +local function getSummon(effectId, caster) if effectId == 't_conjuration_sanguinerose' then return magicData.sanguineRoseDaedra[math.random(#magicData.sanguineRoseDaedra)] + elseif effectId == 't_conjuration_corruptionsummon' then + local target = state.corruption[caster.id] + return target and target.id or 'T_Glb_Cre_Gremlin_01', effectId end return summons[effectId] end I.T_ActorMagic.addEffectStartHandler(function(caster, spell, effect, track) - local creature = getSummon(effect.id) + local creature, tag = getSummon(effect.id, caster) if not creature then return end local id = spell.activeSpellId local index = effect.index local key = toKey(caster, id, index) - state.summons[key] = { id = id, index = index, creatureId = creature, actor = caster } + state.summons[key] = { id = id, index = index, creatureId = creature, actor = caster, tag = tag } caster:sendEvent('T_GetSummonPosition', { key = key }) track.ignore = false end) local function unsummon(creature) if creature:isValid() then + state.corruptionSummons[creature.id] = nil world.vfx.spawn(startVfx, creature.position) creature:remove() end @@ -64,6 +70,10 @@ I.T_ActorMagic.addEffectEndHandler(function(actor, id, index) state.summons[key] = nil end) +local function blockActivation() + return false +end + return { engineHandlers = { onSave = function() @@ -82,6 +92,20 @@ return { end end state.summons = summons + local corruption = {} + for _, actorData in pairs(data.corruption) do + if actorData.actor:isValid() then + corruption[actorData.actor.id] = actorData + end + end + state.corruption = corruption + local corruptionSummons = {} + for _, actor in pairs(data.corruptionSummons) do + if actor:isValid() then + corruptionSummons[actor.id] = actor + end + end + state.corruptionSummons = corruptionSummons end end }, @@ -95,10 +119,14 @@ return { local caster = effect.actor creature:teleport(caster.cell.name, data.position, { onGround = true }) creature:sendEvent('StartAIPackage', { type = 'Follow', target = caster }) - creature:sendEvent('T_MarkSummon', { index = effect.index, id = effect.id, caster = caster }) + creature:sendEvent('T_MarkSummon', { index = effect.index, id = effect.id, caster = caster, tag = effect.tag }) creature:sendEvent('AddVfx', { model = startVfx }) effect.creatureId = nil effect.creature = creature + if effect.tag == 't_conjuration_corruptionsummon' then + state.corruptionSummons[creature.id] = creature + I.Activation.addHandlerForObject(creature, blockActivation) + end end, T_Unsummon = function(data) unsummon(data.creature) @@ -109,5 +137,19 @@ return { I.T_ActorMagic.removeEffect(data.caster, effect.id, effect.index) end end + }, + interfaceName = 'T_SummonMagic', + interface = { + version = 1, + setCorruptedId = function(caster, id) + if id then + state.corruption[caster.id] = { actor = caster, id = id } + else + state.corruption[caster.id] = nil + end + end, + isCorruptionSummon = function(actor) + return state.corruptionSummons[actor.id] or false + end } } diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 354daf7..1759d6a 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -202,6 +202,8 @@ local function addMiscEffects() --addMiscEffect('T_alteration_RadShield', {}) -- Requires a (more elegant) way of applying variable magnitude blind effects addMiscEffect('T_restoration_ArmorResartus', { hasDuration = false }) addMiscEffect('T_restoration_WeaponResartus', { hasDuration = false }) + addMiscEffect('T_conjuration_Corruption', { allowsSpellmaking = false, allowsEnchanting = false, hasDuration = false, onSelf = false, onTarget = true, onTouch = true, harmful = true, unreflectable = true }) + addMiscEffect('T_conjuration_CorruptionSummon', { allowsSpellmaking = false, allowsEnchanting = false }) addMiscEffect('T_illusion_DistractCreature', { unreflectable = true, onSelf = false, harmful = false }) addMiscEffect('T_illusion_DistractHumanoid', { unreflectable = true, onSelf = false, harmful = false }) addMiscEffect('T_restoration_FortifyCasting', {}) From 352562ad59134faad2745fab7a1709e99a935425 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 27 Apr 2026 17:10:59 +0200 Subject: [PATCH 21/28] Implement Wabbajack --- Data Files/MWSE/mods/TamrielData/magic.lua | 13 +- .../MWSE/mods/TamrielData/magicdata.lua | 12 ++ Data Files/l10n/TamrielData/en.yaml | 2 + Data Files/l10n/TamrielData/fr.yaml | 2 + Data Files/l10n/TamrielData/pl.yaml | 8 ++ .../scripts/TamrielData/actor_magic.lua | 37 ++++++ .../scripts/TamrielData/global_magic.lua | 12 +- .../scripts/TamrielData/global_miscspells.lua | 120 +++++++++++++++++- Data Files/scripts/TamrielData/load_magic.lua | 2 + 9 files changed, 188 insertions(+), 20 deletions(-) diff --git a/Data Files/MWSE/mods/TamrielData/magic.lua b/Data Files/MWSE/mods/TamrielData/magic.lua index 24982f0..ec7cee5 100644 --- a/Data Files/MWSE/mods/TamrielData/magic.lua +++ b/Data Files/MWSE/mods/TamrielData/magic.lua @@ -214,17 +214,6 @@ local raceSkeletonBodyParts = { { "T_Yne_Ynesai", "T_B_GazeVeloth_Skeleton_01", "T_C_GazeVeloth_Skeleton_01" }, -- Imga and Tsaesci skeletons will take more effort } -local wabbajackCreatures = { - "T_Mw_UNI_GrahlWabbajack", -- This version of the Grahl does not have fireregenScript attached to it; I saw a crash occur while it was being executed, but I am not sure why. - "scamp", - "T_Glb_Cre_LandDreu_01", - "T_Glb_Cre_TrollCave_03", - "mudcrab", - "T_Ham_Fau_Goat_01", - "Rat", - "golden saint" -} - -- actor id local gazeOfVelothImmuneActors = { ["vivec_god"] = true, @@ -2416,7 +2405,7 @@ local function wabbajackEffect(e) local targetFatigue = target.mobile.fatigue.normalized local targetMagicka = target.mobile.magicka.normalized - local transformCreature = tes3.getObject(wabbajackCreatures[math.random(#wabbajackCreatures)]) + local transformCreature = tes3.getObject(magicData.wabbajackCreatures[math.random(#magicData.wabbajackCreatures)]) local transformedTarget = tes3.createReference({ object = transformCreature, position = target.position, orientation = target.orientation, cell = target.cell }) -- Could this setup and the WabbajackTrans effect actually be done through a summon like the Corruption effect does? transformedTarget.data.tamrielData = transformedTarget.data.tamrielData or {} diff --git a/Data Files/MWSE/mods/TamrielData/magicdata.lua b/Data Files/MWSE/mods/TamrielData/magicdata.lua index bfd3a24..35bfa31 100644 --- a/Data Files/MWSE/mods/TamrielData/magicdata.lua +++ b/Data Files/MWSE/mods/TamrielData/magicdata.lua @@ -708,6 +708,17 @@ local safeScripts = { ["TR_m3_q_vampambush"] = true, } +local wabbajackCreatures = { + "T_Mw_UNI_GrahlWabbajack", -- This version of the Grahl does not have fireregenScript attached to it; I saw a crash occur while it was being executed, but I am not sure why. + "scamp", + "T_Glb_Cre_LandDreu_01", + "T_Glb_Cre_TrollCave_03", + "mudcrab", + "T_Ham_Fau_Goat_01", + "Rat", + "golden saint" +} + return { td_summon_effects = td_summon_effects, td_bound_effects = td_bound_effects, @@ -724,4 +735,5 @@ return { distractedVoiceLines = distractedVoiceLines, sanguineRoseDaedra = sanguineRoseDaedra, safeScripts = safeScripts, + wabbajackCreatures = wabbajackCreatures, } diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index e9a6d78..7bcaa99 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -26,6 +26,8 @@ TamrielData_magic_passwallDoorExterior: "You cannot leave a confined space." TamrielData_magic_passwallUnderwater: "You cannot be underwater." Magic_corruptionScript: "{target} cannot be corrupted." Magic_corruptionSummon: "You cannot corrupt a being summoned by the Skull of Corruption." +Magic_wabbajackFailure: "{target} is too strong to be Wabbajacked!" +Magic_wabbajackAlready: "{target} is already Wabbajacked!" Magic_summonDevourer: "Summon Devourer" Magic_summonDevourerDesc: "This effect summons a devourer from Oblivion. It appears six feet in front of the caster and attacks any entity that attacks the caster until the effect ends or the summoning is killed. At death, or when the effect ends, the summoning disappears, returning to Oblivion. If summoned in town, the guards will attack you and the summoning on sight." diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index 2980e3b..c9706d5 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -26,6 +26,8 @@ TamrielData_magic_passwallDoorExterior: "Vous ne pouvez pas quitter un espace co TamrielData_magic_passwallUnderwater: "Vous ne pouvez pas vous trouver sous l'eau." Magic_corruptionScript: "{target} ne peut être corrompu." Magic_corruptionSummon: "Vous ne pouvez corrompre un être invoqué par le Crâne de Corruption." +Magic_wabbajackFailure: "{target} est trop puissant pour que Wabbajack l'affecte !" +Magic_wabbajackAlready: "{target} est déjà sous l'effet de Wabbajack !" Magic_summonDevourer: "Appel de consummeur" Magic_summonDevourerDesc: "Cet effet permet d'invoquer un consummeur des Royaumes extérieurs. Il apparaît à 2 mètres du lanceur et attaque toute entité hostile à son maître jusqu'à ce que le sort prenne fin ou qu'il soit tué. Quand il meurt ou que le sort prend fin, il disparaît et retourne dans les Royaumes extérieurs. En cas d'invocation en ville, les gardes vous attaqueront, vous et votre invocation." diff --git a/Data Files/l10n/TamrielData/pl.yaml b/Data Files/l10n/TamrielData/pl.yaml index d439009..55050b4 100644 --- a/Data Files/l10n/TamrielData/pl.yaml +++ b/Data Files/l10n/TamrielData/pl.yaml @@ -3,6 +3,14 @@ TamrielData_main_imgaHelm: "Samce Imga nie mogą nosić hełmów." Settings_TamrielData_page01Main_group02Magic_summoningSpells: "Nowe Zaklęcia Przywołania" Settings_TamrielData_page01Main_group02Magic_summoningSpells_Description: "Dodaje nowe zaklęcia przywołania używające istoty dodane w Tamriel Data, takie jak Pożeracze, Herny, Mroczne Uwodzicielki czy Auroranie.\nWymaga ponownego wczytania gry.\n\nDomyślnie: Wł.\n\n" +TamrielData_magic_passwallWard: "Nie możesz tam przeniknąć." +TamrielData_magic_passwallAlpha: "Nie możesz przez to przeniknąć." +TamrielData_magic_passwallExterior: "Musisz być w zamkniętej przestrzeni." +TamrielData_magic_passwallDoorExterior: "Musisz pozostać w zamkniętej przestrzeni." +TamrielData_magic_passwallUnderwater: "Nie możesz być pod wodą." +Magic_wabbajackFailure: "Cel: {target} jest zbyt potężny, nie może być poddany efektowi Łabadżaka!" +Magic_wabbajackAlready: "Cel: {target} jest już pod wpływem efektu Łabadżaka!" + Magic_summonDevourer: "Przywołanie Pożeracza" Magic_summonDevourerDesc: "Przywołuje z Otchłani pożeracza. Stworzenie pojawi się sześć stóp przed magiem i będzie atakowało każdą zagrażającą mu istotę, póki nie minie czas trwania efektu lub póki samo nie zostanie zabite. W obydwu przypadkach przywołana istota znika, powracając do Otchłani. Pamiętaj, że jeśli przywołasz istotę z innego wymiaru wewnątrz miasta, miejska straż rzuci się na ciebie bez ostrzeżenia!" Magic_summonDremoraArcher: "Przywołanie Dremory-Łucznika" diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 88fffff..a0cde78 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -206,6 +206,43 @@ return { I.AI.startPackage({ type = 'Travel', destPosition = state.distract.origin, cancelOther = true, isRepeat = false }) end end + end, + T_MarkWabbajack = function(data) + local dynamic = types.Actor.stats.dynamic + for _, key in pairs({ 'health', 'magicka', 'fatigue' }) do + local stat = dynamic[key](self) + local v = stat.base * data[key] + if v < 2 and key == 'health' then + v = 2 + end + stat.current = v + end + core.sound.playSound3d('alteration hit', self, { loop = false }) + activeSpells:add({ id = 'T_Dae_Alt_UNI_WabbajackTrans', effects = { 0 }, ignoreResistances = true, ignoreSpellAbsorption = true, ignoreReflect = true, caster = data.caster }) + end, + T_EndWabbajack = function(data) + local dynamic = types.Actor.stats.dynamic + local kill = false + for _, key in pairs({ 'health', 'magicka', 'fatigue' }) do + local stat = dynamic[key](self) + local v = stat.base * data[key] + if key == 'health' and v < 2 then + kill = data[key] <= 0 + v = 2 + end + stat.current = v + end + core.sound.playSound3d('alteration hit', self, { loop = false }) + if kill then + -- makes crime work + -- TODO: !5302 + types.Actor._onHit(self, { + damage = { health = 999 }, + sourceType = I.Combat.ATTACK_SOURCE_TYPES.Unspecified, + attacker = data.caster, + successful = true + }) + end end } } diff --git a/Data Files/scripts/TamrielData/global_magic.lua b/Data Files/scripts/TamrielData/global_magic.lua index f1640c8..38c7000 100644 --- a/Data Files/scripts/TamrielData/global_magic.lua +++ b/Data Files/scripts/TamrielData/global_magic.lua @@ -18,9 +18,9 @@ local function onEffectStart(actor, spell, effect) return track.ignore end -local function onEffectUpdate(actor, spell, effect) +local function onEffectUpdate(actor, spell, effect, dt) local track = { ignore = false } - auxUtil.callEventHandlers(effectUpdateHandlers, actor, spell, effect, track) + auxUtil.callEventHandlers(effectUpdateHandlers, actor, spell, effect, dt, track) return track.ignore end @@ -92,7 +92,7 @@ local function getActorState(actor, init) end -- This should all be replaced with built in OpenMW stuff, but that doesn't exist yet. This code cannot track effect lifecycles properly -local function updateEffects(actor, state, tempState) +local function updateEffects(actor, state, tempState, dt) local canDiscard = true state.delayUpdateChecks = true local active = {} @@ -129,7 +129,7 @@ local function updateEffects(actor, state, tempState) canDiscard = false end if s == STATE_ACTIVE then - if onEffectUpdate(actor, spell, effect) then + if onEffectUpdate(actor, spell, effect, dt) then effects[index] = STATE_IGNORE else state.delayUpdateChecks = false @@ -169,7 +169,7 @@ local function waitOrUpdate(actor, dt) tempState.waited = tempState.waited - MAX_WAIT end end - updateEffects(actor, state, tempState) + updateEffects(actor, state, tempState, dt) end local activeActors = world.activeActors @@ -203,7 +203,7 @@ return { if not state then return end - local discard = updateEffects(actor, state, tempState) + local discard = updateEffects(actor, state, tempState, 0) if discard then deleteActorState(actor) end diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index 672cc28..cfe3d88 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -7,6 +7,7 @@ end local I = require('openmw.interfaces') local types = require('openmw.types') +local util = require('openmw.util') local world = require('openmw.world') local l10n = core.l10n('TamrielData') local magicData = require('MWSE.mods.TamrielData.magicdata') @@ -23,6 +24,7 @@ local safeScripts = {} for k, v in pairs(magicData.safeScripts) do safeScripts[k:lower()] = v end +local wabbajackVfx = types.Static.records['T_VFX_Wabbajack'].model local function triggerCrimeIfTrespassing(data) if not data.targetObject or not data.targetObject.owner or not types.Lockable.isLocked(data.targetObject) then @@ -168,7 +170,8 @@ local function toKey(actor, id, index) end local state = { - effects = {} + effects = {}, + wabbajack = {} } local function store(target, spell, effect) @@ -190,6 +193,14 @@ local function distract(type) end end +local function getName(record) + local name = record.name + if not name or name == '' then + return record.id + end + return name +end + local onStart = { t_mysticism_passwall = function(target, spell, effect, track) if types.Player.objectIsInstance(target) then @@ -200,6 +211,61 @@ local onStart = { t_mysticism_reflectdmg = function(target, spell, effect, track) track.ignore = false end, + t_alteration_wabbajack = function(target, spell, effect, track) + local record = target.type.records[target.recordId] + if types.Creature.objectIsInstance(target) and not record.canWalk and not record.isBiped or types.Player.objectIsInstance(target) then + target.type.activeEffects(target):remove(effect.id) + restoreCharge(spell.item, spell.caster) + return + end + local level = target.type.stats.level(target).current + if level >= 30 then + if spell.caster and types.Player.objectIsInstance(spell.caster) then + spell.caster:sendEvent('ShowMessage', { message = l10n('Magic_wabbajackFailure', { target = getName(record) }) }) + core.sound.playSound3d('Spell Failure Alteration', target, { loop = false }) + end + target.type.activeEffects(target):remove(effect.id) + restoreCharge(spell.item, spell.caster) + return + end + local data = state.wabbajack[target.id] + if data then + if spell.caster and types.Player.objectIsInstance(spell.caster) then + spell.caster:sendEvent('ShowMessage', { message = l10n('Magic_wabbajackAlready', { target = data.name }) }) + core.sound.playSound3d('Spell Failure Alteration', target, { loop = false }) + end + target.type.activeEffects(target):remove(effect.id) + restoreCharge(spell.item, spell.caster) + return + end + local maxDuration = 16 + local minDuration = 4 + local effectiveLevel = 0 + if level > 5 then + effectiveLevel = level - 5 + end + local duration = maxDuration - (maxDuration - minDuration) * (effectiveLevel / 24) + local creature = world.createObject(magicData.wabbajackCreatures[math.random(#magicData.wabbajackCreatures)]) + data = { name = getName(record), target = target, duration = duration, actor = creature, caster = spell.caster } + state.wabbajack[creature.id] = data + creature:teleport(target.cell.name, target.position, target.rotation) + local event = { caster = spell.caster } + local dynamic = types.Actor.stats.dynamic + for _, key in pairs({ 'health', 'magicka', 'fatigue' }) do + local stat = dynamic[key](target) + event[key] = stat.current / math.max(stat.base, 1) + end + creature:sendEvent('T_MarkWabbajack', event) + creature:sendEvent('StartAIPackage', { type = 'Follow', target = target }) -- Makes the guards ignore the creature + creature:sendEvent('StartAIPackage', { type = 'Combat', target = spell.caster, cancelOther = false }) + creature:sendEvent('AddVfx', { model = wabbajackVfx }) + target:teleport('T_Wabbajack', util.vector3(0, 0, 53.187)) + track.ignore = false + end, + t_alteration_wabbajackhelper = function(target, spell, effect, track) + store(target, spell, effect) + track.ignore = false + end, t_restoration_armorresartus = function(target, spell, effect, track) resartusEquipment(target, effect.magnitudeThisFrame, types.Armor) track.ignore = false @@ -225,7 +291,7 @@ local onStart = { else if types.Player.objectIsInstance(spell.caster) then local record = target.type.records[target.recordId] - spell.caster:sendEvent('ShowMessage', { message = l10n('Magic_corruptionScript', { target = record.name or record.id }) }) + spell.caster:sendEvent('ShowMessage', { message = l10n('Magic_corruptionScript', { target = getName(record) }) }) end target.type.activeEffects(target):remove(effect.id) restoreCharge(spell.item, spell.caster) @@ -241,6 +307,21 @@ local onStart = { end, } +local onUpdate = { + t_alteration_wabbajackhelper = function(target, spell, effect, dt, track) + -- TODO: replace this with a regular duration when possible and make the affect apply once + local data = state.wabbajack[target.id] + if data then + data.duration = data.duration - dt + if data.duration <= 0 then + target.type.activeEffects(target):remove(effect.id) + end + else + track.ignore = true + end + end +} + local onEnd = { t_restoration_fortifycasting = function(effect) local activeEffects = effect.actor.type.activeEffects(effect.actor) @@ -251,6 +332,27 @@ local onEnd = { end, t_illusion_distracthumanoid = function(effect) effect.actor:sendEvent('T_DistractFinished', effect.effect) + end, + t_alteration_wabbajackhelper = function(effect) + local creature = effect.actor + local data = state.wabbajack[creature.id] + if not data then + return + end + state.wabbajack[creature.id] = nil + local target = data.target + if target:isValid() then + target:teleport(creature.cell.name, creature.position, creature.rotation) + local event = { caster = data.caster } + local dynamic = types.Actor.stats.dynamic + for _, key in pairs({ 'health', 'magicka', 'fatigue' }) do + local stat = dynamic[key](creature) + event[key] = stat.current / math.max(stat.base, 1) + end + target:sendEvent('T_EndWabbajack', event) + target:sendEvent('AddVfx', { model = wabbajackVfx }) + end + creature:remove() end } @@ -261,6 +363,13 @@ I.T_ActorMagic.addEffectStartHandler(function(target, spell, effect, track) end end) +I.T_ActorMagic.addEffectUpdateHandler(function(target, spell, effect, dt, track) + local handler = onUpdate[effect.id] + if handler then + handler(target, spell, effect, dt, track) + end +end) + I.T_ActorMagic.addEffectEndHandler(function(actor, id, index) local key = toKey(actor, id, index) local effect = state.effects[key] @@ -289,6 +398,13 @@ return { end end state.effects = effects + local wabbajack = {} + for _, actorData in pairs(data.wabbajack) do + if actorData.actor:isValid() then + wabbajack[actor.id] = actorData + end + end + state.wabbajack = wabbajack end end }, diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 1759d6a..d752570 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -200,6 +200,8 @@ local function addMiscEffects() addMiscEffect('T_mysticism_ReflectDmg', {}) --addMiscEffect('T_mysticism_DetHuman', {}) -- Requires map dehardcoding --addMiscEffect('T_alteration_RadShield', {}) -- Requires a (more elegant) way of applying variable magnitude blind effects + addMiscEffect('T_alteration_Wabbajack', { allowsSpellmaking = false, allowsEnchanting = false, hasDuration = false, onSelf = false, onTarget = true, onTouch = true, harmful = true, unreflectable = true, hitSound = 'T_SndObj_Silence', hitStatic = 'T_VFX_Empty', areaSound = 'T_SndObj_Silence', areaStatic = 'T_VFX_Empty' }) + addMiscEffect('T_alteration_WabbajackHelper', { isAppliedOnce = false, allowsSpellmaking = false, allowsEnchanting = false, onSelf = false, onTarget = true, onTouch = true, unreflectable = true, hitSound = 'T_SndObj_Silence', hitStatic = 'T_VFX_Empty' }) addMiscEffect('T_restoration_ArmorResartus', { hasDuration = false }) addMiscEffect('T_restoration_WeaponResartus', { hasDuration = false }) addMiscEffect('T_conjuration_Corruption', { allowsSpellmaking = false, allowsEnchanting = false, hasDuration = false, onSelf = false, onTarget = true, onTouch = true, harmful = true, unreflectable = true }) From ba0268a9b1ff9041359f3c5abf621660f9eef30b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 27 Apr 2026 22:43:16 +0200 Subject: [PATCH 22/28] Mostly implement Banish --- Data Files/Tamriel_Data.omwscripts | 1 + Data Files/l10n/TamrielData/en.yaml | 1 + Data Files/l10n/TamrielData/fr.yaml | 1 + Data Files/l10n/TamrielData/pl.yaml | 1 + .../scripts/TamrielData/actor_magic.lua | 37 ++++++++++++++++ .../scripts/TamrielData/container_banish.lua | 31 +++++++++++++ .../scripts/TamrielData/global_miscspells.lua | 44 ++++++++++++++++++- .../scripts/TamrielData/global_summons.lua | 2 +- Data Files/scripts/TamrielData/load_magic.lua | 22 ++++++++-- 9 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 Data Files/scripts/TamrielData/container_banish.lua diff --git a/Data Files/Tamriel_Data.omwscripts b/Data Files/Tamriel_Data.omwscripts index 9e404c1..377eec8 100644 --- a/Data Files/Tamriel_Data.omwscripts +++ b/Data Files/Tamriel_Data.omwscripts @@ -10,3 +10,4 @@ GLOBAL: scripts/TamrielData/global_summons.lua PLAYER, NPC, CREATURE: scripts/TamrielData/actor_magic.lua PLAYER, NPC, CREATURE: scripts/TamrielData/actor_summons.lua LOAD: scripts/TamrielData/load_magic.lua +CUSTOM: scripts/TamrielData/container_banish.lua diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 7bcaa99..13a570f 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -24,6 +24,7 @@ TamrielData_magic_passwallAlpha: "You cannot pass through that." TamrielData_magic_passwallExterior: "You must be in a confined space." TamrielData_magic_passwallDoorExterior: "You cannot leave a confined space." TamrielData_magic_passwallUnderwater: "You cannot be underwater." +Magic_banishFailure: "This spell is too weak to banish {target}!" Magic_corruptionScript: "{target} cannot be corrupted." Magic_corruptionSummon: "You cannot corrupt a being summoned by the Skull of Corruption." Magic_wabbajackFailure: "{target} is too strong to be Wabbajacked!" diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index c9706d5..f42b39c 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -24,6 +24,7 @@ TamrielData_magic_passwallAlpha: "Vous ne pouvez pas passer à travers ceci." TamrielData_magic_passwallExterior: "Vous devez vous trouver dans un espace confiné." TamrielData_magic_passwallDoorExterior: "Vous ne pouvez pas quitter un espace confiné." TamrielData_magic_passwallUnderwater: "Vous ne pouvez pas vous trouver sous l'eau." +Magic_banishFailure: "Ce sort est trop faible pour bannir : {target} !" Magic_corruptionScript: "{target} ne peut être corrompu." Magic_corruptionSummon: "Vous ne pouvez corrompre un être invoqué par le Crâne de Corruption." Magic_wabbajackFailure: "{target} est trop puissant pour que Wabbajack l'affecte !" diff --git a/Data Files/l10n/TamrielData/pl.yaml b/Data Files/l10n/TamrielData/pl.yaml index 55050b4..9236950 100644 --- a/Data Files/l10n/TamrielData/pl.yaml +++ b/Data Files/l10n/TamrielData/pl.yaml @@ -8,6 +8,7 @@ TamrielData_magic_passwallAlpha: "Nie możesz przez to przeniknąć." TamrielData_magic_passwallExterior: "Musisz być w zamkniętej przestrzeni." TamrielData_magic_passwallDoorExterior: "Musisz pozostać w zamkniętej przestrzeni." TamrielData_magic_passwallUnderwater: "Nie możesz być pod wodą." +Magic_banishFailure: "Cel: {target} nie może zostać wypędzony, zaklęcie jest zbyt słabe!" Magic_wabbajackFailure: "Cel: {target} jest zbyt potężny, nie może być poddany efektowi Łabadżaka!" Magic_wabbajackAlready: "Cel: {target} jest już pod wpływem efektu Łabadżaka!" diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index a0cde78..12b3059 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -1,3 +1,4 @@ +local animation = require('openmw.animation') local core = require('openmw.core') local I = require('openmw.interfaces') local nearby = require('openmw.nearby') @@ -158,6 +159,10 @@ return { eventHandlers = { Died = function() state.distract = nil + if state.banish then + core.sendGlobalEvent('T_BanishCorpse', { actor = self.object, height = state.banish }) + state.banish = nil + end end, T_Distract = function(data) local active = I.AI.getActivePackage() @@ -243,6 +248,38 @@ return { successful = true }) end + end, + T_AttemptBanish = function(data) + for _, actor in pairs(I.AI.getTargets('Follow')) do -- Could check Escort as well, I guess + if actor == data.caster then + return + end + end + local targetLevel = types.Actor.stats.level(self).current + local health = types.Actor.stats.dynamic.health(self) + if data.magnitude < targetLevel / 2 * (1 + health.current / math.max(health.base, 1)) then + I.AI.startPackage('Combat', { target = data.caster }) + if types.Player.objectIsInstance(data.caster) then + local record = self.type.records[self.recordId] + local name = record.name + if not name or name == '' then + name = record.id + end + data.caster:sendEvent('ShowMessage', { message = l10n('Magic_banishFailure', { target = name }) }) + end + return + end + activeEffects:remove('soultrap') + state.banish = self:getBoundingBox().halfSize.z * 2 -- get height before collapsing + I.AnimationController.addPlayBlendedAnimationHandler(function(groupName, options) + if groupName:find('death') then + options.speed = 100 + end + end) + health.current = 0 + local model = types.Static.records['T_VFX_Banish'].model + core.sendGlobalEvent('SpawnVfx', { model = model, position = self.position }) + core.sound.playSound3d('mysticism hit', self) -- TODO !3029 end } } diff --git a/Data Files/scripts/TamrielData/container_banish.lua b/Data Files/scripts/TamrielData/container_banish.lua new file mode 100644 index 0000000..933069e --- /dev/null +++ b/Data Files/scripts/TamrielData/container_banish.lua @@ -0,0 +1,31 @@ +local core = require('openmw.core') +local self = require('openmw.self') + +local state + +local timer = math.random() + +return { + engineHandlers = { + onInit = function(light) + state = light + end, + onSave = function() + return state + end, + onLoad = function(data) + state = data + end, + onUpdate = function(dt) + timer = timer + dt + if timer < 1 then + return + end + timer = timer - 1 + for _, item in pairs(self.type.inventory(self):getAll()) do + return + end + core.sendGlobalEvent('T_BanishContainer', { container = self.object, light = state }) + end + } +} diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index cfe3d88..746664f 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -137,6 +137,33 @@ local function playDistractedVoiceLine(data) end end +local function banishCorpse(data) + local actor = data.actor + local items = {} + for _, item in pairs(actor.type.inventory(actor):getAll()) do + -- TODO: filter items. Don't copy MWSE, it doesn't account for scripted items + table.insert(items, item) + end + if #items > 0 then + local container = world.createObject('T_Glb_BanishDae_Empty') + for _, item in pairs(items) do + item:moveInto(container) + end + local rotation = util.transform.rotateZ(actor.rotation:getYaw()) + local position = actor.position + util.vector3(0, 0, data.height) + container:teleport(actor.cell, position, rotation) + local light = world.createObject('T_Glb_BanishDae_Light') + light:teleport(actor.cell, position, rotation) + container:addScript('scripts/TamrielData/container_banish.lua', light) + end + actor:teleport('T_Banish', util.vector3(0, 0, 0)) +end + +local function banishContainer(data) + data.container:remove() + data.light:remove() +end + local function canBeCorrupted(target) if types.Player.objectIsInstance(target) then return false @@ -208,6 +235,17 @@ local onStart = { target:sendEvent('T_Passwall_Cast', effect.magnitudeThisFrame) end end, + t_mysticism_banishdae = function(target, spell, effect, track) + if types.Creature.objectIsInstance(target) and not state.wabbajack[target.id] then + local record = types.Creature.records[target.recordId] + if record.type == types.Creature.TYPE.Daedra then + track.ignore = false + target:sendEvent('T_AttemptBanish', { caster = spell.caster, magnitude = effect.magnitudeThisFrame }) + return + end + end + target.type.activeEffects(target):remove(effect.id) + end, t_mysticism_reflectdmg = function(target, spell, effect, track) track.ignore = false end, @@ -248,7 +286,7 @@ local onStart = { local creature = world.createObject(magicData.wabbajackCreatures[math.random(#magicData.wabbajackCreatures)]) data = { name = getName(record), target = target, duration = duration, actor = creature, caster = spell.caster } state.wabbajack[creature.id] = data - creature:teleport(target.cell.name, target.position, target.rotation) + creature:teleport(target.cell, target.position, target.rotation) local event = { caster = spell.caster } local dynamic = types.Actor.stats.dynamic for _, key in pairs({ 'health', 'magicka', 'fatigue' }) do @@ -342,7 +380,7 @@ local onEnd = { state.wabbajack[creature.id] = nil local target = data.target if target:isValid() then - target:teleport(creature.cell.name, creature.position, creature.rotation) + target:teleport(creature.cell, creature.position, creature.rotation) local event = { caster = data.caster } local dynamic = types.Actor.stats.dynamic for _, key in pairs({ 'health', 'magicka', 'fatigue' }) do @@ -411,5 +449,7 @@ return { eventHandlers = { T_Passwall_teleportPlayer = teleportPlayer, T_DistractVoice = playDistractedVoiceLine, + T_BanishCorpse = banishCorpse, + T_BanishContainer = banishContainer, } } diff --git a/Data Files/scripts/TamrielData/global_summons.lua b/Data Files/scripts/TamrielData/global_summons.lua index 30d7d85..2977ad8 100644 --- a/Data Files/scripts/TamrielData/global_summons.lua +++ b/Data Files/scripts/TamrielData/global_summons.lua @@ -117,7 +117,7 @@ return { end local creature = world.createObject(effect.creatureId) local caster = effect.actor - creature:teleport(caster.cell.name, data.position, { onGround = true }) + creature:teleport(caster.cell, data.position, { onGround = true }) creature:sendEvent('StartAIPackage', { type = 'Follow', target = caster }) creature:sendEvent('T_MarkSummon', { index = effect.index, id = effect.id, caster = caster, tag = effect.tag }) creature:sendEvent('AddVfx', { model = startVfx }) diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index d752570..dbdae19 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -196,18 +196,32 @@ local function addMiscEffects() onTarget = false, onTouch = false, hasDuration = false, cost = magicData.td_misc_effects.T_mysticism_Passwall[2] * 0.5 -- compensate for MWSE using area instead of magnitude }) - --addMiscEffect('T_mysticism_BanishDae', {}) -- Requires a way to trigger death without waiting for the animation + addMiscEffect('T_mysticism_BanishDae', { + hasDuration = false, hasMagnitude = true, unreflectable = true, hitSound = 'T_SndObj_Silence', + hitStatic = 'T_VFX_Empty', areaSound = 'T_SndObj_Silence', areaStatic = 'T_VFX_Empty' + }) -- Requires a way to trigger death without waiting for the animation addMiscEffect('T_mysticism_ReflectDmg', {}) --addMiscEffect('T_mysticism_DetHuman', {}) -- Requires map dehardcoding --addMiscEffect('T_alteration_RadShield', {}) -- Requires a (more elegant) way of applying variable magnitude blind effects - addMiscEffect('T_alteration_Wabbajack', { allowsSpellmaking = false, allowsEnchanting = false, hasDuration = false, onSelf = false, onTarget = true, onTouch = true, harmful = true, unreflectable = true, hitSound = 'T_SndObj_Silence', hitStatic = 'T_VFX_Empty', areaSound = 'T_SndObj_Silence', areaStatic = 'T_VFX_Empty' }) - addMiscEffect('T_alteration_WabbajackHelper', { isAppliedOnce = false, allowsSpellmaking = false, allowsEnchanting = false, onSelf = false, onTarget = true, onTouch = true, unreflectable = true, hitSound = 'T_SndObj_Silence', hitStatic = 'T_VFX_Empty' }) + addMiscEffect('T_alteration_Wabbajack', { + allowsSpellmaking = false, allowsEnchanting = false, hasDuration = false, onSelf = false, onTarget = true, + onTouch = true, harmful = true, unreflectable = true, hitSound = 'T_SndObj_Silence', hitStatic = 'T_VFX_Empty', + areaSound = 'T_SndObj_Silence', areaStatic = 'T_VFX_Empty' + }) + addMiscEffect('T_alteration_WabbajackHelper', { + isAppliedOnce = false, allowsSpellmaking = false, allowsEnchanting = false, onSelf = false, onTarget = true, + onTouch = true, unreflectable = true, hitSound = 'T_SndObj_Silence', hitStatic = 'T_VFX_Empty' + }) addMiscEffect('T_restoration_ArmorResartus', { hasDuration = false }) addMiscEffect('T_restoration_WeaponResartus', { hasDuration = false }) - addMiscEffect('T_conjuration_Corruption', { allowsSpellmaking = false, allowsEnchanting = false, hasDuration = false, onSelf = false, onTarget = true, onTouch = true, harmful = true, unreflectable = true }) + addMiscEffect('T_conjuration_Corruption', { + allowsSpellmaking = false, allowsEnchanting = false, hasDuration = false, onSelf = false, + onTarget = true, onTouch = true, harmful = true, unreflectable = true + }) addMiscEffect('T_conjuration_CorruptionSummon', { allowsSpellmaking = false, allowsEnchanting = false }) addMiscEffect('T_illusion_DistractCreature', { unreflectable = true, onSelf = false, harmful = false }) addMiscEffect('T_illusion_DistractHumanoid', { unreflectable = true, onSelf = false, harmful = false }) + --addMiscEffect('T_mysticism_Blink', { hasDuration = false, hitSound = 'T_SndObj_BlinkHit', hitStatic = 'T_VFX_Empty' }) -- Requires the ability to check if levitation is disabled addMiscEffect('T_restoration_FortifyCasting', {}) addMiscEffect('T_conjuration_SanguineRose', { allowsSpellmaking = false, allowsEnchanting = false }) end From 492ac475f118678af87e116975685923da1d06de Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 28 Apr 2026 17:45:44 +0200 Subject: [PATCH 23/28] Finish Banish for now --- Data Files/scripts/TamrielData/actor_magic.lua | 3 ++- Data Files/scripts/TamrielData/global_miscspells.lua | 9 +++++++-- Data Files/scripts/TamrielData/load_magic.lua | 10 +++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 12b3059..98ca9a5 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -5,6 +5,7 @@ local nearby = require('openmw.nearby') local self = require('openmw.self') local types = require('openmw.types') local auxUtil = require('openmw_aux.util') +local l10n = core.l10n('TamrielData') local FT_TO_UNITS = 22.1 @@ -258,7 +259,7 @@ return { local targetLevel = types.Actor.stats.level(self).current local health = types.Actor.stats.dynamic.health(self) if data.magnitude < targetLevel / 2 * (1 + health.current / math.max(health.base, 1)) then - I.AI.startPackage('Combat', { target = data.caster }) + I.AI.startPackage({ type = 'Combat', target = data.caster }) if types.Player.objectIsInstance(data.caster) then local record = self.type.records[self.recordId] local name = record.name diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index 746664f..d8ba300 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -139,10 +139,15 @@ end local function banishCorpse(data) local actor = data.actor + if not actor.type.isDead(actor) then + return -- Somehow Palpascamp returned + end local items = {} for _, item in pairs(actor.type.inventory(actor):getAll()) do - -- TODO: filter items. Don't copy MWSE, it doesn't account for scripted items - table.insert(items, item) + -- TODO: filter items. Don't copy MWSE, it doesn't account for script added items + if not types.Ingredient.objectIsInstance(item) then + table.insert(items, item) + end end if #items > 0 then local container = world.createObject('T_Glb_BanishDae_Empty') diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index dbdae19..963df85 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -175,7 +175,7 @@ local function addSummons() if template == 'callBear' then template = 'summonbear' end - effects[id] = { template = effects[template], cost = cost, icon = icon, name = t(name), description = t(desc), allowsSpellmaking = true, allowsEnchanting = true } + effects[id] = { template = effects[template], baseCost = cost, icon = icon, name = t(name), description = t(desc), allowsSpellmaking = true, allowsEnchanting = true } implementedEffects[id] = true end end @@ -185,7 +185,7 @@ local function addMiscEffects() local function addMiscEffect(id, params) local name, cost, icon, desc, template = unpack(magicData.td_misc_effects[id]) params.name = t(name) - params.cost = params.cost or cost + params.baseCost = params.baseCost or cost params.icon = icon params.description = t(desc) params.template = effects[template] @@ -194,12 +194,12 @@ local function addMiscEffects() end addMiscEffect('T_mysticism_Passwall', { onTarget = false, onTouch = false, hasDuration = false, - cost = magicData.td_misc_effects.T_mysticism_Passwall[2] * 0.5 -- compensate for MWSE using area instead of magnitude + baseCost = magicData.td_misc_effects.T_mysticism_Passwall[2] * 0.5 -- compensate for MWSE using area instead of magnitude }) addMiscEffect('T_mysticism_BanishDae', { hasDuration = false, hasMagnitude = true, unreflectable = true, hitSound = 'T_SndObj_Silence', - hitStatic = 'T_VFX_Empty', areaSound = 'T_SndObj_Silence', areaStatic = 'T_VFX_Empty' - }) -- Requires a way to trigger death without waiting for the animation + hitStatic = 'T_VFX_Empty', areaSound = 'T_SndObj_Silence', areaStatic = 'T_VFX_Empty', harmful = false + }) addMiscEffect('T_mysticism_ReflectDmg', {}) --addMiscEffect('T_mysticism_DetHuman', {}) -- Requires map dehardcoding --addMiscEffect('T_alteration_RadShield', {}) -- Requires a (more elegant) way of applying variable magnitude blind effects From 7bfa24a153c6f5a575d675432da204646399c7b8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 28 Apr 2026 21:34:25 +0200 Subject: [PATCH 24/28] Implement Blink --- .../scripts/TamrielData/actor_magic.lua | 21 +++++++++++++++++++ .../scripts/TamrielData/global_miscspells.lua | 7 +++++++ Data Files/scripts/TamrielData/load_magic.lua | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 98ca9a5..2561a9a 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -4,10 +4,12 @@ local I = require('openmw.interfaces') local nearby = require('openmw.nearby') local self = require('openmw.self') local types = require('openmw.types') +local util = require('openmw.util') local auxUtil = require('openmw_aux.util') local l10n = core.l10n('TamrielData') local FT_TO_UNITS = 22.1 +local BLINK_COLLISION = nearby.COLLISION_TYPE.AnyPhysical + nearby.COLLISION_TYPE.VisualOnly - nearby.COLLISION_TYPE.Water local activeEffects = self.type.activeEffects(self) local activeSpells = self.type.activeSpells(self) @@ -281,6 +283,25 @@ return { local model = types.Static.records['T_VFX_Banish'].model core.sendGlobalEvent('SpawnVfx', { model = model, position = self.position }) core.sound.playSound3d('mysticism hit', self) -- TODO !3029 + end, + T_Blink = function(magnitude) + -- TODO: check if levitation is disabled + local range = magnitude * FT_TO_UNITS + local halfExtents = self.type.getPathfindingAgentBounds(self).halfExtents + local start = self.position + util.vector3(0, 0, halfExtents.z * 1.4) + local destination = start + self.rotation * util.vector3(0, range, 0) + local result = nearby.castRay(start, destination, { ignore = self, collisionType = BLINK_COLLISION }) + local options + if result.hit then + destination = result.hitPos - self.rotation * util.vector3(0, halfExtents.y + 16, 0) + end + if self.cell.isExterior then + local height = core.land.getHeightAt(destination, self.cell) + if destination.z < height then + options = { onGround = true } + end + end + core.sendGlobalEvent('T_Teleport', { object = self.object, cell = self.cell.id, position = destination, options = options }) end } } diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index d8ba300..7a087a5 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -342,6 +342,10 @@ local onStart = { end, t_illusion_distractcreature = distract(types.Creature), t_illusion_distracthumanoid = distract(types.NPC), + t_mysticism_blink = function(target, spell, effect, track) + target:sendEvent('T_Blink', effect.magnitudeThisFrame) + track.ignore = false + end, t_restoration_fortifycasting = function(target, spell, effect, track) local activeEffects = target.type.activeEffects(target) activeEffects:modify(-effect.magnitudeThisFrame, 'sound') @@ -456,5 +460,8 @@ return { T_DistractVoice = playDistractedVoiceLine, T_BanishCorpse = banishCorpse, T_BanishContainer = banishContainer, + T_Teleport = function(data) + data.object:teleport(world.getCellById(data.cell), data.position, data.options or data.rotation) + end } } diff --git a/Data Files/scripts/TamrielData/load_magic.lua b/Data Files/scripts/TamrielData/load_magic.lua index 963df85..a20030c 100644 --- a/Data Files/scripts/TamrielData/load_magic.lua +++ b/Data Files/scripts/TamrielData/load_magic.lua @@ -221,7 +221,7 @@ local function addMiscEffects() addMiscEffect('T_conjuration_CorruptionSummon', { allowsSpellmaking = false, allowsEnchanting = false }) addMiscEffect('T_illusion_DistractCreature', { unreflectable = true, onSelf = false, harmful = false }) addMiscEffect('T_illusion_DistractHumanoid', { unreflectable = true, onSelf = false, harmful = false }) - --addMiscEffect('T_mysticism_Blink', { hasDuration = false, hitSound = 'T_SndObj_BlinkHit', hitStatic = 'T_VFX_Empty' }) -- Requires the ability to check if levitation is disabled + addMiscEffect('T_mysticism_Blink', { hasDuration = false, hitSound = 'T_SndObj_BlinkHit', hitStatic = 'T_VFX_Empty' }) addMiscEffect('T_restoration_FortifyCasting', {}) addMiscEffect('T_conjuration_SanguineRose', { allowsSpellmaking = false, allowsEnchanting = false }) end From 7bfe18ff5bda40c181a6f104e7b261bca155cdf2 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 29 Apr 2026 16:31:46 +0200 Subject: [PATCH 25/28] Implement Blink indicator --- Data Files/l10n/TamrielData/en.yaml | 2 + Data Files/l10n/TamrielData/fr.yaml | 2 + .../scripts/TamrielData/actor_magic.lua | 19 +--- .../scripts/TamrielData/actor_magic_blink.lua | 32 +++++++ .../scripts/TamrielData/global_miscspells.lua | 9 ++ .../scripts/TamrielData/player_magic.lua | 90 +++++++++++++++++++ .../TamrielData/utils/feature_data.lua | 6 ++ 7 files changed, 144 insertions(+), 16 deletions(-) create mode 100644 Data Files/scripts/TamrielData/actor_magic_blink.lua diff --git a/Data Files/l10n/TamrielData/en.yaml b/Data Files/l10n/TamrielData/en.yaml index 13a570f..d2a955a 100644 --- a/Data Files/l10n/TamrielData/en.yaml +++ b/Data Files/l10n/TamrielData/en.yaml @@ -7,6 +7,8 @@ Settings_TamrielData_page01Main_group02Magic_miscSpells: "Add New Miscellaneous Settings_TamrielData_page01Main_group02Magic_miscSpells_Description: "Adds new spells that do not fit into usual categories." Settings_TamrielData_page01Main_group02Magic_summoningSpells: "Add New Summoning Spells" Settings_TamrielData_page01Main_group02Magic_summoningSpells_Description: "Adds new summoning spells using creatures added by Tamriel Data, such as Devourers, Herne, Dark Seducers, and Aurorans.\nRequires restart.\n\nDefault: On\n\n" +Settings_TamrielData_page01Main_group02Magic_miscBlinkIndicator: "Indicator for Blink" +Settings_TamrielData_page01Main_group02Magic_miscBlinkIndicator_Description: "Shows where Tamriel Data's Blink effect will teleport you to." Settings_TamrielData_page01Main_group99Misc: "Miscellaneous" Settings_TamrielData_page01Main_group99Misc_debugLogging: "Debug Logging" Settings_TamrielData_page01Main_group99Misc_debugLogging_Description: "Adds more logs, for example in F10 OpenMW logs. Enable this if you want to report an issue with the scripts, so that you can provide more information about it." diff --git a/Data Files/l10n/TamrielData/fr.yaml b/Data Files/l10n/TamrielData/fr.yaml index f42b39c..0a65906 100644 --- a/Data Files/l10n/TamrielData/fr.yaml +++ b/Data Files/l10n/TamrielData/fr.yaml @@ -7,6 +7,8 @@ Settings_TamrielData_page01Main_group02Magic_miscSpells: "Ajout de nouveaux sort Settings_TamrielData_page01Main_group02Magic_miscSpells_Description: "Ajoute de nouveaux sorts qui ne rentrent pas dans les catégories habituelles." Settings_TamrielData_page01Main_group02Magic_summoningSpells: "Ajout de nouveaux sorts d'Invocation" Settings_TamrielData_page01Main_group02Magic_summoningSpells_Description: "Ajoute de nouveaux sorts d'Invocation utilisant des créatures de Ressources communes de Tamriel comme les consummeurs, les hernes, les sombres séductrices, et les auroriens.\nRequiert un redémarrage.\n\nPar défaut : activé\n\n" +Settings_TamrielData_page01Main_group02Magic_miscBlinkIndicator: "Indicateur pour l'effet de Transfert" +Settings_TamrielData_page01Main_group02Magic_miscBlinkIndicator_Description: "Indique où l'effet magique de Transfert du mod vous téléportera quand vous le lancerez." Settings_TamrielData_page01Main_group99Misc: "Divers" Settings_TamrielData_page01Main_group99Misc_debugLogging: "Journalisation des messages de débug" Settings_TamrielData_page01Main_group99Misc_debugLogging_Description: "Ajoute plus de logs, par exemple dans les logs d'OpenMW affichés avec la touche F10. Activez cette option si vous souhaitez signaler un problème avec les scripts afin de pouvoir fournir plus d'informations à ce sujet." diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 2561a9a..311721a 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -7,9 +7,9 @@ local types = require('openmw.types') local util = require('openmw.util') local auxUtil = require('openmw_aux.util') local l10n = core.l10n('TamrielData') +local helpers = require('scripts.TamrielData.actor_magic_blink') local FT_TO_UNITS = 22.1 -local BLINK_COLLISION = nearby.COLLISION_TYPE.AnyPhysical + nearby.COLLISION_TYPE.VisualOnly - nearby.COLLISION_TYPE.Water local activeEffects = self.type.activeEffects(self) local activeSpells = self.type.activeSpells(self) @@ -286,21 +286,8 @@ return { end, T_Blink = function(magnitude) -- TODO: check if levitation is disabled - local range = magnitude * FT_TO_UNITS - local halfExtents = self.type.getPathfindingAgentBounds(self).halfExtents - local start = self.position + util.vector3(0, 0, halfExtents.z * 1.4) - local destination = start + self.rotation * util.vector3(0, range, 0) - local result = nearby.castRay(start, destination, { ignore = self, collisionType = BLINK_COLLISION }) - local options - if result.hit then - destination = result.hitPos - self.rotation * util.vector3(0, halfExtents.y + 16, 0) - end - if self.cell.isExterior then - local height = core.land.getHeightAt(destination, self.cell) - if destination.z < height then - options = { onGround = true } - end - end + local destination, options = helpers.getBlinkDestination(magnitude) + -- TODO: don't use teleportation and preserve momentum core.sendGlobalEvent('T_Teleport', { object = self.object, cell = self.cell.id, position = destination, options = options }) end } diff --git a/Data Files/scripts/TamrielData/actor_magic_blink.lua b/Data Files/scripts/TamrielData/actor_magic_blink.lua new file mode 100644 index 0000000..8af1f57 --- /dev/null +++ b/Data Files/scripts/TamrielData/actor_magic_blink.lua @@ -0,0 +1,32 @@ +local core = require('openmw.core') +local nearby = require('openmw.nearby') +local self = require('openmw.self') +local util = require('openmw.util') + +local FT_TO_UNITS = 22.1 +local BLINK_COLLISION = nearby.COLLISION_TYPE.AnyPhysical + nearby.COLLISION_TYPE.VisualOnly - nearby.COLLISION_TYPE.Water - nearby.COLLISION_TYPE.Projectile + +local function getBlinkDestination(magnitude) + local range = magnitude * FT_TO_UNITS + local halfExtents = self.type.getPathfindingAgentBounds(self).halfExtents + local start = self.position + util.vector3(0, 0, halfExtents.z * 1.4) + local destination = start + self.rotation * util.vector3(0, range, 0) + local result = nearby.castRay(start, destination, { ignore = self, collisionType = BLINK_COLLISION }) + local options + local ground + if result.hit then + destination = result.hitPos - self.rotation * util.vector3(0, halfExtents.y + 16, 0) + end + if self.cell.isExterior then + local height = core.land.getHeightAt(destination, self.cell) + if destination.z < height then + ground = height + options = { onGround = true } + end + end + return destination, options, ground +end + +return { + getBlinkDestination = getBlinkDestination +} diff --git a/Data Files/scripts/TamrielData/global_miscspells.lua b/Data Files/scripts/TamrielData/global_miscspells.lua index 7a087a5..ffcd750 100644 --- a/Data Files/scripts/TamrielData/global_miscspells.lua +++ b/Data Files/scripts/TamrielData/global_miscspells.lua @@ -462,6 +462,15 @@ return { T_BanishContainer = banishContainer, T_Teleport = function(data) data.object:teleport(world.getCellById(data.cell), data.position, data.options or data.rotation) + end, + T_BlinkIndicator = function(data) + local vfxId = 'T_BlinkIndicator' .. data.actor.id + world.vfx.remove(vfxId) + if data.position then + local options = { vfxId = vfxId, loop = true } + world.vfx.spawn('meshes/td/td_vfx_blink_indicator.nif', data.position, options) + world.vfx.spawn('meshes/td/td_vfx_blink_ground.nif', data.groundPos or data.position, options) + end end } } diff --git a/Data Files/scripts/TamrielData/player_magic.lua b/Data Files/scripts/TamrielData/player_magic.lua index 8b52822..2b70cc9 100644 --- a/Data Files/scripts/TamrielData/player_magic.lua +++ b/Data Files/scripts/TamrielData/player_magic.lua @@ -3,11 +3,101 @@ local core = require('openmw.core') if not core.magic.effects.records['T_mysticism_Passwall'] then return end +local nearby = require('openmw.nearby') +local self = require('openmw.self') +local types = require('openmw.types') +local util = require('openmw.util') +local Actor = types.Actor +local version_check = require('scripts.TamrielData.utils.version_check') local magic_passwall = require('scripts.TamrielData.player_magic_passwall') +local magic_blink = require('scripts.TamrielData.actor_magic_blink') + +local blinkOn = false +local cachedMagnitude = { + spell = {}, + item = {} +} + +local function getBlinkMagnitude() + local spell = Actor.getSelectedSpell(self) + local effects + local cache + if spell then + cache = cachedMagnitude.spell + if cache.id == spell.id then + return cache.magnitude + else + cache.id = spell.id + cache.magnitude = nil + end + effects = spell.effects + else + local item = Actor.getSelectedEnchantedItem(self) + if not item then + return + end + cache = cachedMagnitude.item + if cache.id == item.recordId then + return cache.magnitude + else + cache.id = item.recordId + cache.magnitude = nil + end + local record = item.type.records[item.recordId] + if record.enchant then + local enchantment = core.magic.enchantments.records[record.enchant] + effects = enchantment and enchantment.effects + end + end + if not effects then + return + end + for _, effect in pairs(effects) do + if effect.id == 't_mysticism_blink' and effect.range == core.magic.RANGE.Self then + cache.magnitude = effect.magnitudeMin + return cache.magnitude + end + end +end + +local function showBlinkIndicator() + -- TODO: switch to RTT/UI + if Actor.getStance(self) == Actor.STANCE.Spell then + local magnitude = getBlinkMagnitude() + if magnitude then + local destination, _, ground = magic_blink.getBlinkDestination(magnitude) + local groundPos + if ground then + groundPos = util.vector3(destination.x, destination.y, ground + 6) + else + local result = nearby.castRay(destination, util.vector3(destination.x, destination.y, destination.z - 7168), { ignore = self }) + groundPos = result.hitPos + util.vector3(0, 0, 6) + end + local position = util.vector3(destination.x, destination.y, destination.z + 24) + core.sendGlobalEvent('T_BlinkIndicator', { position = position, groundPos = groundPos, actor = self }) + blinkOn = true + return true + end + end + return false +end return { eventHandlers = { T_Passwall_Cast = magic_passwall.onCastPasswall + }, + engineHandlers = { + onUpdate = function() + if version_check.isFeatureEnabled('blinkIndicator') then + if showBlinkIndicator() then + return + end + end + if blinkOn then + blinkOn = false + core.sendGlobalEvent('T_BlinkIndicator', { actor = self }) + end + end } } diff --git a/Data Files/scripts/TamrielData/utils/feature_data.lua b/Data Files/scripts/TamrielData/utils/feature_data.lua index 11bdc25..351c913 100644 --- a/Data Files/scripts/TamrielData/utils/feature_data.lua +++ b/Data Files/scripts/TamrielData/utils/feature_data.lua @@ -24,6 +24,12 @@ features["summoningSpells"] = { settingsEnabledByDefault = true, settingsKey = "Settings_TamrielData_page01Main_group02Magic_summoningSpells" } +features["blinkIndicator"] = { + requiredLuaApi = 129, + settingsPlayerSectionStorageId = "Settings_TamrielData_page01Main_group02Magic", + settingsEnabledByDefault = true, + settingsKey = "Settings_TamrielData_page01Main_group02Magic_miscBlinkIndicator" +} features["debugLogging"] = { requiredLuaApi = 44, settingsPlayerSectionStorageId = "Settings_TamrielData_page01Main_group99Misc", From 2a4c468bf27641369d2e1b6a8507b840a6632cfc Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 29 Apr 2026 17:03:02 +0200 Subject: [PATCH 26/28] Don't send Min-Tal's sigil into the ceiling --- Data Files/scripts/TamrielData/actor_magic.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data Files/scripts/TamrielData/actor_magic.lua b/Data Files/scripts/TamrielData/actor_magic.lua index 311721a..e0e8f47 100644 --- a/Data Files/scripts/TamrielData/actor_magic.lua +++ b/Data Files/scripts/TamrielData/actor_magic.lua @@ -273,7 +273,7 @@ return { return end activeEffects:remove('soultrap') - state.banish = self:getBoundingBox().halfSize.z * 2 -- get height before collapsing + state.banish = self.type.getPathfindingAgentBounds(self).halfExtents.z * 2 -- yields better results than getBoundingBox I.AnimationController.addPlayBlendedAnimationHandler(function(groupName, options) if groupName:find('death') then options.speed = 100 From 88090bf5b82cc04d282048a36392bf69ed71bf91 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 29 Apr 2026 22:40:33 +0200 Subject: [PATCH 27/28] Improve Blink destination --- .../scripts/TamrielData/actor_magic_blink.lua | 14 +++++++++++++- Data Files/scripts/TamrielData/player_magic.lua | 9 ++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Data Files/scripts/TamrielData/actor_magic_blink.lua b/Data Files/scripts/TamrielData/actor_magic_blink.lua index 8af1f57..c22401b 100644 --- a/Data Files/scripts/TamrielData/actor_magic_blink.lua +++ b/Data Files/scripts/TamrielData/actor_magic_blink.lua @@ -11,12 +11,24 @@ local function getBlinkDestination(magnitude) local halfExtents = self.type.getPathfindingAgentBounds(self).halfExtents local start = self.position + util.vector3(0, 0, halfExtents.z * 1.4) local destination = start + self.rotation * util.vector3(0, range, 0) - local result = nearby.castRay(start, destination, { ignore = self, collisionType = BLINK_COLLISION }) + local rayOptions = { ignore = self, collisionType = BLINK_COLLISION } + local result = nearby.castRay(start, destination, rayOptions) local options local ground if result.hit then destination = result.hitPos - self.rotation * util.vector3(0, halfExtents.y + 16, 0) end + local height = util.vector3(0, 0, halfExtents.z * 2) + result = nearby.castRay(destination, destination + height, rayOptions) + if result.hit then -- bumped into the ceiling + local floor = result.hitPos - height + result = nearby.castRay(result.hitPos, floor, rayOptions) + if result.hit then -- bumped into the floor; no room here + destination = self.position + else + destination = floor + end + end if self.cell.isExterior then local height = core.land.getHeightAt(destination, self.cell) if destination.z < height then diff --git a/Data Files/scripts/TamrielData/player_magic.lua b/Data Files/scripts/TamrielData/player_magic.lua index 2b70cc9..2b695a7 100644 --- a/Data Files/scripts/TamrielData/player_magic.lua +++ b/Data Files/scripts/TamrielData/player_magic.lua @@ -71,10 +71,13 @@ local function showBlinkIndicator() if ground then groundPos = util.vector3(destination.x, destination.y, ground + 6) else - local result = nearby.castRay(destination, util.vector3(destination.x, destination.y, destination.z - 7168), { ignore = self }) - groundPos = result.hitPos + util.vector3(0, 0, 6) + groundPos = util.vector3(destination.x, destination.y, destination.z - 7168) + local result = nearby.castRay(destination, groundPos, { ignore = self }) + if result.hitPos then + groundPos = result.hitPos + util.vector3(0, 0, 6) + end end - local position = util.vector3(destination.x, destination.y, destination.z + 24) + local position = util.vector3(destination.x, destination.y, (ground or destination.z) + 24) core.sendGlobalEvent('T_BlinkIndicator', { position = position, groundPos = groundPos, actor = self }) blinkOn = true return true From e0252c102ab02632ff56dc29ef8b03227c3793a0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 30 Apr 2026 16:43:13 +0200 Subject: [PATCH 28/28] Improve Blink some more --- Data Files/scripts/TamrielData/actor_magic_blink.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Data Files/scripts/TamrielData/actor_magic_blink.lua b/Data Files/scripts/TamrielData/actor_magic_blink.lua index c22401b..a13f999 100644 --- a/Data Files/scripts/TamrielData/actor_magic_blink.lua +++ b/Data Files/scripts/TamrielData/actor_magic_blink.lua @@ -22,6 +22,7 @@ local function getBlinkDestination(magnitude) result = nearby.castRay(destination, destination + height, rayOptions) if result.hit then -- bumped into the ceiling local floor = result.hitPos - height + rayOptions.ignore = result.hitObject result = nearby.castRay(result.hitPos, floor, rayOptions) if result.hit then -- bumped into the floor; no room here destination = self.position