myspace / Lua /CharacterEffect.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
15.3 kB
UndefineClass("CharacterEffect")
UndefineClass("StatusEffect")
UndefineClass("Perk")
DefineClass("CharacterEffect", "Modifiable", "CharacterEffectProperties")
DefineClass("StatusEffect", "CharacterEffect")
DefineClass("Perk", "CharacterEffect", "PerkProperties")
const.DbgStatusEffects = false
function CharacterEffect:ResolveValue(key)
local value = self:GetProperty(key)
if value then return value end
-- Check in the instance parameters first.
if self.InstParameters then
local found = table.find_value(self.InstParameters, "Name", key)
if found then
return found.Value
end
end
-- Check in the template
local template = CharacterEffectDefs[self.class]
return template and template:ResolveValue(key)
end
function CharacterEffect:__toluacode(indent, pstr, GetPropFunc)
if not pstr then
return string.format("PlaceCharacterEffect('%s', %s)", self.class, ObjPropertyListToLuaCode(self, indent, GetPropFunc))
end
pstr:appendf("PlaceCharacterEffect('%s', ", self.class)
ObjPropertyListToLuaCode(self, indent, GetPropFunc, pstr)
return pstr:append(")")
end
function GetCharacterEffectId(self)
if IsKindOf(self, "CharacterEffect") then
return self.class
end
if IsKindOf(self, "CharacterEffectCompositeDef") then
return self.id
end
end
-- CompositeDef code
DefineClass.CharacterEffectCompositeDef = {
__parents = { "CompositeDef", "MsgActorReactionsPreset" },
-- Composite def
ObjectBaseClass = "CharacterEffect",
ComponentClass = false,
-- Preset
EditorMenubarName = "Character Effect Editor",
EditorMenubar = "Combat",
EditorMenubarSortKey = "-8",
EditorShortcut = "",
EditorIcon = "CommonAssets/UI/Icons/atom molecule science.png",
EditorPreview = Untranslated("<Group> <StatValue>"),
GlobalMap = "CharacterEffectDefs",
Documentation = CompositeDef.Documentation .. "\n\nCreates a new character effect preset that could be added/removed from a unit.",
HasParameters = true,
HasSortKey = true,
-- 'true' is much faster, but it doesn't call property setters & clears default properties upon saving
StoreAsTable = false,
-- Serialize props as an array => {key, value, key value}
store_as_obj_prop_list = true
}
DefineModItemCompositeObject("CharacterEffectCompositeDef", {
EditorName = "Character effect",
EditorSubmenu = "Unit",
TestDescription = "Adds the status effect to the selected merc."
})
if config.Mods then
function ModItemCharacterEffectCompositeDef:delete()
CharacterEffectCompositeDef.delete(self)
ModItemCompositeObject.delete(self)
end
function ModItemCharacterEffectCompositeDef:TestModItem(ged)
ModItemCompositeObject.TestModItem(self, ged)
if IsKindOf(SelectedObj, "Unit") then
SelectedObj:AddStatusEffect(self.id)
else
ModLog(T(187070922299, "Cannot add the status effect as no unit is selected."))
end
end
end
function CharacterEffectCompositeDef:delete()
MsgReactionsPreset.delete(self)
end
function CharacterEffectCompositeDef:ResolveValue(key)
local value = self:GetProperty(key)
if value then return value end
if self.HasParameters and self.Parameters then
local found = table.find_value(self.Parameters, "Name", key)
if found then
return found.Value
end
end
end
function CharacterEffectCompositeDef:VerifyReaction(event, reaction_def, actor, ...)
if not IsKindOf(actor, "StatusEffectObject") then
return
end
local id = GetCharacterEffectId(self)
if actor and (event == "StatusEffectAdded" or event == "StatusEffectRemoved") then
local _id = select(2, ...)
return id == _id
end
return actor:HasStatusEffect(id)
end
function CharacterEffectCompositeDef:GetReactionActors(event, reaction_def, ...)
local objs = {}
local id = GetCharacterEffectId(self)
for session_id, data in pairs(gv_UnitData) do
local obj = ZuluReactionResolveUnitActorObj(session_id, data)
if obj:HasStatusEffect(id) then
objs[#obj + 1] = obj
end
end
table.sortby_field(objs, "session_id")
return objs
end
-- Overwrite of the old PlaceCharacterEffect
function PlaceCharacterEffect(item_id, instance, ...)
local id = item_id
local class = g_Classes[id]
if not class then
assert(string.format("Class %s not found", id))
-- In case the class was deleted and we're loading an older save file
return PlaceCharacterEffect("MissingEffect", instance, ...)
end
local obj
if CharacterEffectCompositeDef.store_as_obj_prop_list then
obj = class:new({}, ...)
SetObjPropertyList(obj, instance)
else
obj = class:new(instance, ...)
end
return obj
end
-- end of CompositeDef code
DefineClass.StatusEffectObject = {
__parents = { "PropertyObject", "InitDone" },
properties = {
{ id = "StatusEffects", editor = "nested_list", default = false, no_edit = true, },
{ id = "StatusEffectImmunity", editor = "nested_list", default = false, no_edit = true, },
{ id = "StatusEffectReceivedTime", editor = "nested_list", default = false, no_edit = true, },
},
}
function StatusEffectObject:Init()
self.StatusEffects = {}
self.StatusEffectImmunity = {}
self.StatusEffectReceivedTime = {}
end
function StatusEffectObject:UpdateStatusEffectIndex()
local effects = self.StatusEffects
for i, effect in ipairs(effects) do
if effect and effect.class then
effects[effect.class] = i
end
end
end
function StatusEffectObject:GetStatusEffect(id)
local idx = self.StatusEffects[id]
return idx and self.StatusEffects[idx]
end
function StatusEffectObject:HasStatusEffect(id)
return self.StatusEffects[id]
end
function StatusEffectObject:ReportStatusEffectsInLog()
return const.DbgStatusEffects
end
function StatusEffectObject:AddStatusEffectImmunity(effect, reason)
self.StatusEffectImmunity[effect] = self.StatusEffectImmunity[effect] or {}
self.StatusEffectImmunity[effect][reason] = true
self:RemoveStatusEffect(effect)
end
function StatusEffectObject:RemoveStatusEffectImmunity(effect, reason)
if self.StatusEffectImmunity[effect] then
self.StatusEffectImmunity[effect][reason] = nil
if next(self.StatusEffectImmunity[effect]) == nil then
self.StatusEffectImmunity[effect] = nil
end
end
end
function StatusEffectObject:AddStatusEffect(id, stacks)
NetUpdateHash("StatusEffectObject:AddStatusEffect", self, id, IsValid(self) and self:HasMember("GetPos") and self:GetPos())
if self.StatusEffectImmunity[id] or (IsKindOfClasses(self, "Unit", "UnitData") and self:IsDead()) then
return
end
stacks = stacks or 1
local preset = CharacterEffectDefs[id]
local effect = self:GetStatusEffect(id)
local cur_stacks = effect and effect.stacks or 0
if cur_stacks >= preset:GetProperty("max_stacks") then
return
end
local context = {}
context.target_units = {self}
local ok = EvalConditionList(preset:GetProperty("Conditions"), self, context)
if not ok then
return
end
local refresh
local newStack = false
if not effect then
effect = PlaceCharacterEffect(id)
effect.stacks = Min(stacks, effect.max_stacks)
table.insert(self.StatusEffects, effect)
self.StatusEffects[id] = #self.StatusEffects
newStack = true
table.sort(self.StatusEffects, function(a,b)
return CharacterEffectDefs[a.class].SortKey < CharacterEffectDefs[b.class].SortKey
end)
self:UpdateStatusEffectIndex()
for _, mod in ipairs(preset:GetProperty("Modifiers")) do
self:AddModifier("StatusEffect:"..id, mod.target_prop, mod.mod_mul*10, mod.mod_add)
end
self.StatusEffectReceivedTime[id] = GameTime()
self:AddReactions(effect, effect.unit_reactions)
effect:OnAdded(self)
else
newStack = effect.stacks
effect.stacks = Min(effect.stacks + stacks, effect.max_stacks)
newStack = effect.stacks > newStack
refresh = true
end
effect.CampaignTimeAdded = Game.CampaignTime
if Platform.developer and self:ReportStatusEffectsInLog() and newStack then
if not self:IsDead() then
CombatLog("debug", T{Untranslated("<em><effect></em> (<name>)"), name = self:GetLogName(), effect = effect.DisplayName or Untranslated(id)})
end
end
if effect.AddEffectText and effect.AddEffectText ~= "" and not refresh then
if not self:IsDead() then
CombatLog("short", T{effect.AddEffectText, self})
end
end
if IsValid(self) and effect.HasFloatingText and newStack then
CreateMapRealTimeThread(function()
WaitPlayerControl()
CreateFloatingText(self, T{961020758708, "+ <DisplayName>", effect}, nil, nil, true)
end)
end
if effect.lifetime ~= "Indefinite" and IsKindOf(self, "Unit") and g_Combat then
local duration = effect.lifetime == "Until End of Next Turn" and 1 or 0
if g_CurrentTeam and g_Teams[g_CurrentTeam] and not g_Teams[g_CurrentTeam].player_team then
duration = duration + 1
end
self:SetEffectExpirationTurn(id, "expiration", g_Combat.current_turn + duration)
end
ObjModified(self.StatusEffects)
Msg("StatusEffectAdded", self, id, stacks)
self:CallReactions("OnStatusEffectAdded", id, stacks)
return effect
end
function StatusEffectObject:RemoveStatusEffect(id, stacks, reason)
local has = self:HasStatusEffect(id)
if not has then return end
NetUpdateHash("StatusEffectObject:RemoveStatusEffect", self, id, self:HasMember("GetPos") and self:GetPos())
local effect = self.StatusEffects[has]
local preset = CharacterEffectDefs[id]
if not effect.stacks then -- shield from faulty effects
table.remove(self.StatusEffects, has)
self.StatusEffects[id] = nil
self.StatusEffectReceivedTime[id] = nil
self:UpdateStatusEffectIndex()
for _, mod in ipairs(preset:GetProperty("Modifiers")) do
self:RemoveModifier("StatusEffect:"..id, mod.target_prop)
end
self:RemoveReactions(effect)
effect:OnRemoved(self)
return
end
if reason == "death" and effect.dontRemoveOnDeath then
return
end
local lost
local to_remove = (stacks == "all" and effect.stacks) or stacks or 1
local removedStacks = Min(effect.stacks, to_remove)
effect.stacks = Max(0, effect.stacks - to_remove)
if effect.stacks == 0 then
table.remove(self.StatusEffects, has)
self.StatusEffects[id] = nil
self.StatusEffectReceivedTime[id] = nil
self:UpdateStatusEffectIndex()
for _, mod in ipairs(preset:GetProperty("Modifiers")) do
self:RemoveModifier("StatusEffect:"..id, mod.target_prop)
end
self:RemoveReactions(effect)
effect:OnRemoved(self)
lost = true
if Platform.developer and self:ReportStatusEffectsInLog() then
if not self:IsDead() then
CombatLog("debug", T{Untranslated("<name> lost effect <effect>"), name = self:GetLogName(), effect = effect.DisplayName or Untranslated(id)})
end
end
if effect.RemoveEffectText then
if not self:IsDead() then
CombatLog("short", T{effect.RemoveEffectText, self})
end
end
end
ObjModified(self.StatusEffects)
Msg("StatusEffectRemoved", self, id, removedStacks, reason)
self:CallReactions("OnStatusEffectRemoved", id, effect.stacks)
end
function StatusEffectObject:HasVisibleEffects()
for _, effect in ipairs(self.StatusEffects) do
if effect.Shown then
return true
end
end
return false
end
function StatusEffectObject:GetUIVisibleStatusEffects(addBadgeHidden)
local vis = {}
for _, effect in ipairs(self.StatusEffects) do
if effect and effect.Shown and effect.Icon and (addBadgeHidden or not effect.HideOnBadge) then
vis[#vis + 1] = effect
end
end
return vis
end
function StatusEffectObject:RemoveAllCharacterEffects()
while #self.StatusEffects > 0 do
self:RemoveStatusEffect(self.StatusEffects[1].class, "all")
end
end
function StatusEffectObject:RemoveAllStatusEffects(reason)
for i = #self.StatusEffects, 1, -1 do
local effect = self.StatusEffects[i]
if IsKindOf(effect, "StatusEffect") then
self:RemoveStatusEffect(effect.class, "all", reason)
end
end
end
PerkSortTable = {
Personal = 1,
Personality = 2,
Specialization = 3,
Quirk = 4,
Gold = 5,
Silver = 6,
Bronze = 7,
}
function StatusEffectObject:GetPerks(tier_level, sort)
if not self.StatusEffects then return empty_table end
local result = table.ifilter(self.StatusEffects, function(i, s)
return IsKindOf(s, "Perk")
and (not tier_level or s.Tier==tier_level)
end)
if sort then
table.sort(result, function(a, b)
local z = PerkSortTable[a.Tier] or 0
local x = PerkSortTable[b.Tier] or 0
if z==x then
return a.class<b.class
end
return z < x
end)
end
return result
end
function StatusEffectObject:GetPerksByStat(stat)
if not self.StatusEffects or not stat then return empty_table end
return table.ifilter(self.StatusEffects, function(i, s)
return IsKindOf(s, "Perk") and (s.Stat == stat)
end)
end
function StatusEffectObject:HasAnyStatusEffects()
for _, effect in ipairs(self.StatusEffects) do
if IsKindOf(effect, "StatusEffect") then
return true
end
end
return false
end
function PersonalPerkStartingOfButtons(o)
local mercs = {}
ForEachPreset("UnitDataCompositeDef", function(data)
if data.StartingPerks and table.find(data.StartingPerks, o.id) then
mercs[#mercs + 1] = {
name = data.id,
func = function()
data:OpenEditor()
end
}
end
end)
return mercs
end
function StatusEffectObject:StatusEffectsCleanUp()
local effects = self.StatusEffects
local toRemove = {}
for key, value in pairs(effects) do
if type(key) == "string" and not CharacterEffectDefs[key] then
toRemove[#toRemove+1] = value
effects[key] = nil
end
end
for _, idx in ipairs(toRemove) do
table.remove(effects, idx)
end
end
DefineClass.UnitModifier = {
__parents = { "PropertyObject" },
properties = {
{ id = "target_prop", name = "Property Name", editor = "combo", items = function() return ClassModifiablePropsNonTranslatableCombo(g_Classes.Unit) end, default = "" },
{ id = "mod_add", name = "Add", editor = "number", default = 0,},
{ id = "mod_mul", name = "Mul", editor = "number", scale = 100, default = 100 },
},
StoreAsTable = true,
EditorView = Untranslated("Unit Modifier: (<u(target_prop)> + <mod_add>) * <FormatAsFloat(mod_mul, 100, 2)>"),
}
-- Remove marked StatusEffects on SatView Travel
function OnMsg.SquadStartedTravelling(squad)
for _, id in ipairs(squad.units) do
local unit = g_Units[id]
if IsValid(unit) then
for i = #unit.StatusEffects, 1, -1 do
local effect = unit.StatusEffects[i]
if effect.RemoveOnSatViewTravel then
unit:RemoveStatusEffect(effect.class)
end
end
end
local unitData = gv_UnitData[id]
for i = #unitData.StatusEffects, 1, -1 do
local effect = unitData.StatusEffects[i]
if effect.RemoveOnSatViewTravel then
unitData:RemoveStatusEffect(effect.class)
end
end
end
end
-- Remove marked StatusEffects on Campaign Time resume
function OnMsg.CampaignTimeAdvanced(time, ot)
for _, unit in ipairs(g_Units) do
if IsValid(unit) then
for i = #unit.StatusEffects, 1, -1 do
local effect = unit.StatusEffects[i]
if effect.RemoveOnCampaignTimeAdvance or effect.RemoveOnEndCombat then
unit:RemoveStatusEffect(effect.class)
local unitData = gv_UnitData[unit.session_id]
unitData:RemoveStatusEffect(effect.class)
end
end
end
end
end
function OnMsg.ExplorationTick()
for _, unit in ipairs(g_Units) do
for _, effect in ripairs(unit.StatusEffects) do
if effect.RemoveOnEndCombat and (GameTime() > (unit.StatusEffectReceivedTime[effect.class] or 0) + 5000) then
unit:RemoveStatusEffect(effect.class)
end
end
end
end