1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Entities redesign and a few ERM features

* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
This commit is contained in:
AlexVinS
2018-03-17 17:58:30 +03:00
committed by AlexVinS
parent 11bb46780a
commit ecaa9f5d0b
475 changed files with 22491 additions and 7123 deletions

24
scripts/lib/Metatype.lua Normal file
View File

@@ -0,0 +1,24 @@
local T =
{
ARTIFACT = 1,
ARTIFACT_INSTANCE = 2,
CREATURE = 3,
CREATURE_INSTANCE = 4,
FACTION = 5,
HERO_CLASS = 6,
HERO_TYPE = 7,
HERO_INSTANCE = 8,
MAP_OBJECT_GROUP = 9,
MAP_OBJECT_TYPE = 10,
MAP_OBJECT_INSTANCE = 11,
SKILL = 12,
SPELL = 13
}
local M = setmetatable({},
{
__newindex = function() error("Metatype table is immutable") end,
__index = T
})
return M

252
scripts/lib/erm.lua Normal file
View File

@@ -0,0 +1,252 @@
DATA = DATA or {}
local DATA = DATA
local GAME = GAME
local TurnStarted = require("events.TurnStarted")
DATA.ERM = DATA.ERM or {}
local ERM_D = DATA.ERM
ERM_D.F = ERM_D.F or {}
ERM_D.MDATA = ERM_D.MDATA or {}
ERM_D.Q = ERM_D.Q or {}
ERM_D.v = ERM_D.v or {}
ERM_D.z = ERM_D.z or {}
local INT_MT =
{
__index = function(t, key)
return rawget(t, key) or 0
end
}
local STR_MT =
{
__index = function(t, key)
return rawget(t, key) or ""
end
}
setmetatable(DATA.ERM.Q, INT_MT)
setmetatable(DATA.ERM.v, INT_MT)
setmetatable(DATA.ERM.z, STR_MT)
local ERM =
{
w = {},
M = {},
subs = {}
}
local ERM_MT =
{
__index = DATA.ERM
}
setmetatable(ERM, ERM_MT)
local MACROS_MT =
{
__index = function(M, key)
address = rawget(ERM.MDATA, key)
assert(address, "Macro "..key.." is not defined")
return ERM[address.name][address.index]
end,
__newindex = function(M, key, value)
address = rawget(ERM.MDATA, key)
assert(address, "Macro "..key.." is not defined")
ERM[address.name][address.index] = value
end
}
setmetatable(ERM.M, MACROS_MT)
function ERM:addMacro(name, varName, varIndex)
assert(varName == "v" or varName == "z", "Macro for "..varName.. "[...] is not allowed")
rawset(self.MDATA, name, {name = varName, index = varIndex})
end
function ERM:getZVar(p1)
if type(p1) == "number" then
return self.z[tostring(p1)]
else
return p1
end
end
local HERO_VAR_MT =
{
__index = function(t, key)
ERM_D.WDATA = ERM_D.WDATA or {}
local wKey = ERM_D.wKey
if not wKey then
error("Hero variable set is not selected")
end
ERM_D.WDATA[wKey] = ERM_D.WDATA[wKey] or {}
return ERM_D.WDATA[wKey][key] or 0
end,
__newindex = function(t, key, value)
ERM_D.WDATA = ERM_D.WDATA or {}
local wKey = ERM_D.wKey
if not wKey then
error("Hero variable set is not selected")
end
ERM_D.WDATA[wKey] = ERM_D.WDATA[wKey] or {}
ERM_D.WDATA[wKey][key] = value
end
}
setmetatable(ERM.w, HERO_VAR_MT)
local function dailyUpdate(event)
ERM.Q.c = GAME:getDate(0)
end
table.insert(ERM.subs, TurnStarted.subscribeAfter(EVENT_BUS, dailyUpdate))
local Receivers = {}
local function createReceiverLoader(name)
local loader = function(...)
Receivers[name] = Receivers[name] or require("core:erm."..name)
return Receivers[name]:new(ERM, ...)
end
return loader
end
--[[
AR
BA
BF
BG
BH
CA
CD
CE
CM
DL
CO
EA
EX
GE
HL
HO
HT
LE
MO
MR
MW
OB
PM
PO
QW
SS
TL
TR
]]
local supportedReceivers = {"BM", "BU", "DO", "FU", "HE", "IF", "DO", "MA", "MF", "OW", "TM", "UN", "VR"}
for _, v in pairs(supportedReceivers) do ERM[v] = createReceiverLoader(v) end
local Triggers = {}
local function createTriggerLoader(name)
local loader = function(id_list, id_key)
local t = Triggers[id_key]
if not t then
local p = require("core:erm."..name.."_T")
t = p:new{ERM=ERM, id=id_list}
Triggers[id_key] = t
end
return t
end
return loader
end
--[[
!?AE
!?BA
!?BF
!?BG
!?BR
!?CM client only?
!?CO
!?DL client only?
!?GE
!?HE
!?HL
!?HM
!?LE (!$LE)
!?MG
!?MM client only?
!?MR
!?MW
!?OB (!$OB)
!?SN client only?
!?SN (ERA)
!?TH client only?
!?TL client only? depends on time limit feature
]]
local supportedTriggers = {"PI", "FU", "GM", "MF", "TM"}
local TriggerLoaders = {}
for _, v in pairs(supportedTriggers) do TriggerLoaders[v] = createTriggerLoader(v) end
local function makeIdKey(name, id_list)
if id_list == nil then
id_list = {}
elseif type(id_list) ~= "table" then
id_list = {id_list}
end
return name .. table.concat(id_list, "|")
end
function ERM:addTrigger(t)
local name = t.name
local fn = t.fn
local id_list = t.id or {}
local id_key = makeIdKey(name, id_list)
local trigger = TriggerLoaders[name](id_list, id_key)
trigger:addHandler(fn)
end
function ERM:callFunction(id, x)
local id_key = makeIdKey("FU", id)
local t = Triggers[id_key]
if t then
t:call({}, x)
end
end
function ERM:callInstructions(cb)
if not DATA.ERM.instructionsCalled then
cb()
local id_key = makeIdKey("PI")
local pi = Triggers[id_key]
if pi then
pi:call()
end
DATA.ERM.instructionsCalled = true
end
end
return ERM

104
scripts/lib/erm/BM.lua Normal file
View File

@@ -0,0 +1,104 @@
require("battle.Unit")
local battle = BATTLE
local ReceiverBase = require("core:erm.ReceiverBase")
local BM = ReceiverBase:new()
function BM:new(ERM, unitId)
assert(unitId ~= nil, "!!BM requires unit identifier")
return ReceiverBase.new(self,{ERM = ERM, unitId = unitId})
end
function BM:A(x, p1)
if type(p1) == "nil" then
local unit = battle:getUnitById(self.unitId)
return unit:getAttack(false)
else
error("!!BM:A set is not implemented")
end
end
function BM:B(x, ...)
error("!!BM:B is not implemented")
end
function BM:C(x, ...)
error("!!BM:C is not implemented")
end
function BM:D(x, ...)
error("!!BM:D is not implemented")
end
function BM:E(x, ...)
error("!!BM:E is not implemented")
end
function BM:F(x, ...)
error("!!BM:F is not implemented")
end
function BM:G(x, ...)
error("!!BM:G is not implemented")
end
function BM:H(x, ...)
error("!!BM:H is not implemented")
end
function BM:J(x, ...)
error("!!BM:J is not implemented")
end
function BM:K(x, ...)
error("!!BM:K is not implemented")
end
function BM:L(x, ...)
error("!!BM:L is not implemented")
end
function BM:M(x, ...)
error("!!BM:M is not implemented")
end
function BM:N(x, ...)
error("!!BM:N is not implemented")
end
function BM:O(x, ...)
error("!!BM:O is not implemented")
end
function BM:P(x, ...)
error("!!BM:P is not implemented")
end
function BM:Q(x, ...)
error("!!BM:Q is not implemented")
end
function BM:R(x, ...)
error("!!BM:R is not implemented")
end
function BM:S(x, ...)
error("!!BM:S is not implemented")
end
function BM:T(x, ...)
error("!!BM:T is not implemented")
end
function BM:U(x, ...)
error("!!BM:U is not implemented")
end
function BM:V(x, ...)
error("!!BM:V is not implemented")
end
return BM

130
scripts/lib/erm/BU.lua Normal file
View File

@@ -0,0 +1,130 @@
require("battle.Unit")
local ReceiverBase = require("core:erm.ReceiverBase")
local BU = ReceiverBase:new()
function BU:new(ERM)
return ReceiverBase.new(self,{ERM = ERM})
end
local BattleLogMessage = require("netpacks.BattleLogMessage")
local BattleUnitsChanged = require("netpacks.BattleUnitsChanged")
local battle = BATTLE
function BU:C(x, p1)
assert(type(p1) == "nil", "!!BU:C can only check value")
local ret = battle:isFinished()
if type(ret) == "nil" then
return 0
else
return 1
end
end
function BU:D(x, hex, p1)
assert(type(p1) == "nil", "!!BU:D can only check value")
local unit = battle:getUnitByPos(hex, false)
if unit then
if unit:isAlive() then
return nil, -2
else
return nil, unit:unitId()
end
else
return nil, -1
end
end
function BU:E(x, hex, p1)
assert(type(p1) == "nil", "!!BU:E can only check value")
local unit = battle:getUnitByPos(hex, false)
if unit and unit:isAlive() then
return nil, unit:unitId()
else
return nil, -1
end
end
local SPECIAL_FIELDS = {}
SPECIAL_FIELDS[0] = 0
SPECIAL_FIELDS[22] = 1
SPECIAL_FIELDS[9] = 2
SPECIAL_FIELDS[18] = 3
SPECIAL_FIELDS[20] = 4
SPECIAL_FIELDS[19] = 5
SPECIAL_FIELDS[17] = 6
SPECIAL_FIELDS[14] = 7
SPECIAL_FIELDS[15] = 8
SPECIAL_FIELDS[16] = 9
function BU:G(x, p1)
assert(type(p1) == "nil", "!!BU:G? is not implemented")
local bfield = SPECIAL_FIELDS[battle:getBattlefieldType()]
if bfield then
return bfield
else
return -1
end
end
function BU:M(x, message)
local pack = BattleLogMessage.new()
pack:addText(message)
SERVER:addToBattleLog(pack)
end
function BU:O(x, ...)
error("!!BU:O is not implemented")
end
function BU:R(x, ...)
error("!!BU:R is not implemented")
end
function BU:S(x, typ, count, hex, side, slot)
local pack = BattleUnitsChanged.new()
local id = battle:getNextUnitId()
pack:add(id,
{
newUnitInfo =
{
["count"] = count,
["type"] = typ,
["side"] = side,
["position"] = hex,
["summoned"] = (slot == -1),
}
})
SERVER:changeUnits(pack)
end
function BU:T(x)
local tacticDistance = battle:getTacticDistance()
if tacticDistance == 0 then
return 0
else
return 1
end
end
function BU:V(x, ...)
error("!!BU:V is not implemented")
end
return BU

55
scripts/lib/erm/DO.lua Normal file
View File

@@ -0,0 +1,55 @@
local ReceiverBase = require("core:erm.ReceiverBase")
local DO = ReceiverBase:new()
function DO:new(ERM, fun, start, stop, inc)
assert(fun and start and stop and inc, "!!DO requires 4 arguments")
return ReceiverBase.new(self,
{
ERM=ERM,
fun=fun,
start=start,
stop=stop,
inc=inc,
})
end
function DO:P(x, ...)
local argc = select('#', ...)
local newx = {}
local ret = {}
do
local __iter, limit, step = tonumber(self.start), tonumber(self.stop), tonumber(self.inc)
while (step > 0 and __iter <= limit) or (step <= 0 and __iter >= limit) do
for idx = 1, argc do
local v = select(idx, ...)
if v ~= nil then
newx[tostring(idx)] = v
end
end
newx['16'] = __iter
self.ERM:callFunction(self.fun, newx)
__iter = newx['16']
__iter = __iter + step
end
end
for idx = 1, argc do
local v = select(idx, ...)
if v == nil then
ret[idx] = newx[tostring(idx)]
else
ret[idx] = nil
end
end
return unpack(ret)
end
return DO

44
scripts/lib/erm/FU.lua Normal file
View File

@@ -0,0 +1,44 @@
local ReceiverBase = require("core:erm.ReceiverBase")
local FU = ReceiverBase:new()
function FU:new(ERM, fun)
assert(fun, "!!FU requires 1 argument")
return ReceiverBase.new(self,
{
ERM=ERM,
fun=fun,
})
end
function FU:P(x, ...)
local argc = select('#', ...)
local newx = {}
local ret = {}
for idx = 1, argc do
local v = (select(idx, ...))
idx = tostring(idx)
if v ~= nil then
newx[idx] = v
end
end
self.ERM:callFunction(self.fun, newx)
for idx = 1, argc do
local v = (select(idx, ...))
if v == nil then
ret[idx] = newx[tostring(idx)]
else
ret[idx] = nil
end
end
return unpack(ret)
end
return FU

15
scripts/lib/erm/FU_T.lua Normal file
View File

@@ -0,0 +1,15 @@
local TriggerBase = require("core:erm.TriggerBase")
local _
local FU = TriggerBase:new()
function FU:call(p1, x)
self.ERM.activeEvent = nil
self.ERM.activeTrigger = self
for _, fn in ipairs(self.fn) do
fn(self.e, self.y, x)
end
self.ERM.activeTrigger = nil
end
return FU

21
scripts/lib/erm/GM_T.lua Normal file
View File

@@ -0,0 +1,21 @@
local TriggerBase = require("core:erm.TriggerBase")
local GameResumed = require("events.GameResumed")
local trigger = TriggerBase:new()
function trigger:new(o)
o = TriggerBase.new(self, o)
local id1 = o.id[1]
if id1 == 0 or id1 == "0" then
o.sub = GameResumed.subscribeAfter(EVENT_BUS, function(event)
o:call(event)
end)
else
error ("Identifier "..tostring(id1) .. " not supported by !?GM")
end
return o
end
return trigger

181
scripts/lib/erm/HE.lua Normal file
View File

@@ -0,0 +1,181 @@
local ReceiverBase = require("core:erm.ReceiverBase")
local HE = ReceiverBase:new()
function HE:new(ERM, p1, p2, p3)
assert(p1 ~= nil, "!!HE requires hero identifier")
if p2 and p3 then
--by coordinates
error("!!HEx/y/l: form is not implemented")
else
-- assume p1 is identifier
local hero = GAME:getHeroWithSubid(p1)
if not hero then
logError("Hero with id ".. tostring(p1) .. " not found")
end
return ReceiverBase.new(self,
{
id=p1,
ERM=ERM,
hero=hero
})
end
end
function HE:A(x, ...)
--artifacts
logError("!!HE:A is not implemented")
end
HE:p1Dispatcher("B")
function HE:B0(x, ...)
--name
logError("!!HE:B is not implemented")
end
function HE:B1(x, ...)
--bio
logError("!!HE:B is not implemented")
end
function HE:B2(x, ...)
--class
logError("!!HE:B is not implemented")
end
function HE:B3(x, _)
--get default bio
logError("!!HE:B is not implemented")
end
function HE:C(x, ...)
local argc = select("#", ...)
if argc == 14 then
return self:C14(x, ...)
elseif argc == 4 then
local N = select(1, ...)
return nil, self["C"..tostring(N)](self, x, select(2, ...))
else
logError("!!HE:C extended form is not implemented")
end
end
function HE:C0(x, slot, typ, count)
--change creatures by slot
if typ~=nil or count ~=nil then
logError("!!HE:C0 set is not implemented")
return
end
local stack = self.hero:getStack(slot)
if not stack then
return nil, -1, 0
else
return nil, stack:getType():getIndex(), stack:getCount()
end
end
function HE:C1(x, ...)
--change creatures by type
end
function HE:C05(x, ...)
--change creatures by slot adv
end
function HE:C15(x, ...)
--change creatures by type adv
end
function HE:C2(x, ...)
--add creatures
end
function HE:C14(x, ...)
-- change creatures with Query
end
function HE:D(x, ...)
logError("!!HE:D not implemented")
end
function HE:E(x, ...)
logError("!!HE:E is not implemented")
end
function HE:F(x, ...)
logError("!!HE:F is not implemented")
end
function HE:G(x, ...)
logError("!!HE:G is not implemented")
end
function HE:H(x, ...)
logError("!!HE:H is not implemented")
end
function HE:I(x, ...)
logError("!!HE:I is not implemented")
end
function HE:K(x, ...)
logError("!!HE:K is not implemented")
end
function HE:L(x, ...)
logError("!!HE:L is not implemented")
end
function HE:M(x, ...)
logError("!!HE:M is not implemented")
end
function HE:N(x, ...)
logError("!!HE:N is not implemented")
end
function HE:O(x, owner)
if owner~=nil then
logError("!!HE:O set is not implemented")
return
else
if not self.hero then
return 255
end
return self.hero:getOwner()
end
end
function HE:P(x, ...)
logError("!!HE:P is not implemented")
end
function HE:R(x, ...)
logError("!!HE:R is not implemented")
end
function HE:S(x, ...)
logError("!!HE:S is not implemented")
end
function HE:T(x, ...)
logError("!!HE:T is not implemented")
end
function HE:U(x, ...)
logError("!!HE:U is not implemented")
end
function HE:V(x, ...)
logError("!!HE:V is not implemented")
end
function HE:W(x, ...)
logError("!!HE:A is not implemented")
end
function HE:X(x, ...)
logError("!!HE:X is not implemented")
end
function HE:Y(x, ...)
logError("!!HE:Y is ot implemented")
end
return HE

85
scripts/lib/erm/IF.lua Normal file
View File

@@ -0,0 +1,85 @@
local InfoWindow = require("netpacks.InfoWindow")
local ReceiverBase = require("core:erm.ReceiverBase")
local IF = ReceiverBase:new()
function IF:new(ERM)
return ReceiverBase.new(self,{ERM = ERM})
end
--[[
according to the ERM help:
"%%" -> "%"
"%F#" -> current value of # flag.
"%Vf"..."%Vt" -> current value of corresponding variable.
"%W1"..."%W100" -> current value of corresponding hero variable.
"%X1"..."%X16" -> current value of corresponding function parameter.
"%Y1"..."%Y100" -> current value of corresponding local variable.
"%Z1"..."%Z500" -> current value of corresponding string variable.
"%$macro$" -> macro name of corresponding variable
"%Dd" -> current day of week
"%Dw" -> current week
"%Dm" -> current month
"%Da" -> current day from beginning of the game
"%Gc" -> the color of current gamer in text
]]
function IF:M(x, message)
local pack = InfoWindow.new()
pack:setPlayer(GAME:getCurrentPlayer())
local _self = self
local onMatch1 = function (key1, key2)
if key1 == 'X' then
return x[key2]
end
if key1 == 'V' then
return _self.ERM.v[key2]
end
if key1 == 'Z' then
return _self.ERM.z[key2]
end
if key1 == 'F' then
return _self.ERM.F[key2]
end
return nil
end
local onMatch2 = function (key1, key2)
if key1 == 'V' then
return _self.ERM.Q[key2]
end
return nil
end
message = string.gsub(message, "%%([FVWXYZ])([%d]+)", onMatch1)
message = string.gsub(message, "%%(V)(%w)", onMatch2)
message = string.gsub(message, "(%%)(%%)", "%1")
pack:addText(message)
SERVER:commitPackage(pack)
end
function IF:W(x, heroIndex)
if heroIndex == nil then
return tonumber(DATA.ERM.wKey or -2)
else
if heroIndex == -1 then
error("IF:W-1 not implemented")
end
DATA.ERM.wKey = tostring(heroIndex)
end
end
return IF

233
scripts/lib/erm/MA.lua Normal file
View File

@@ -0,0 +1,233 @@
local logError = logError
local bit = bit
local ReceiverBase = require("core:erm.ReceiverBase")
local Metatype = require ("core:Metatype")
local EntitiesChanged = require("netpacks.EntitiesChanged")
local Bonus = require("Bonus")
local BonusBearer = require("BonusBearer")
local BonusList = require("BonusList")
local RES = {[0] = "wood", [1] = "mercury", [2] = "ore", [3] = "sulfur", [4] = "crystal", [5] = "gems", [6] = "gold", [7] = "mithril"}
local SERVICES = SERVICES
local creatures = SERVICES:creatures()
local SERVER = SERVER
local getCreatureByIndex = creatures.getByIndex
local function creatureByIndex(index)
return getCreatureByIndex(creatures, index)
end
local function sendChanges(creatureIndex, data)
local pack = EntitiesChanged.new()
pack:update(Metatype.CREATURE, creatureIndex, data)
SERVER:commitPackage(pack)
end
local MA = ReceiverBase:new()
function MA:new(ERM)
return ReceiverBase.new(self,{ERM = ERM})
end
local function checkCreatureIndex(creatureIndex)
assert(creatureIndex ~= nil, "!!MA requires creature identifier")
if type(creatureIndex) == "string" then
error("Identifier resolving is not implemented")
end
return creatureIndex
end
local function createModifier(scope, jsonKey, getterKey)
local f = function (self, x, creatureIndex, p1)
local creatureIndex = checkCreatureIndex(creatureIndex)
if p1 == nil then
local creature = creatureByIndex(creatureIndex)
return nil, creature[getterKey](creature)
else
local packData = {config = {}}
local config = packData.config
for _, v in ipairs(scope) do
config[v] = {}
config = config[v]
end
config[jsonKey] = p1
sendChanges(creatureIndex, packData)
return
end
end
return f
end
MA.A = createModifier({}, "attack", "getBaseAttack")
MA.B = createModifier({}, "spellPoints" ,"getBaseSpellPoints")
MA.D = createModifier({}, "defense" ,"getBaseDefense")
MA.E = createModifier({"damage"}, "max", "getBaseDamageMax")
MA.F = createModifier({}, "fightValue" ,"getFightValue")
MA.G = createModifier({}, "growth" ,"getGrowth")
MA.H = createModifier({"advMapAmount"}, "max" ,"getAdvMapAmountMax")
MA.I = createModifier({}, "aiValue" ,"getAIValue")
MA.L = createModifier({}, "level" , "getLevel")
MA.M = createModifier({"damage"}, "min", "getBaseDamageMin")
MA.N = createModifier({}, "shots" , "getBaseShots")
MA.O = createModifier({}, "faction" ,"getFactionIndex")
MA.P = createModifier({}, "hitPoints" ,"getBaseHitPoints")
MA.R = createModifier({}, "horde" , "getHorde")
MA.S = createModifier({}, "speed" , "getBaseSpeed")
MA.V = createModifier({"advMapAmount"}, "min","getAdvMapAmountMin")
function MA:C(x, creatureIndex, resIndex, cost)
local creatureIndex = checkCreatureIndex(creatureIndex)
if cost == nil then
local creature = creatureByIndex(creatureIndex)
return nil, nil, creature:getCost(resIndex)
else
local packData = {config = {cost = {[RES[resIndex]] = cost}}}
sendChanges(creatureIndex, packData)
end
end
function MA:U(x, creatureIndex, upgradeIndex)
-- -2 - no upgrade
-- -1 - usual upgrade
logError("!!MA:U is not implemented")
end
local FLAG_NAMES =
{
--[1] = "doubleWide", --0
[2] = "FLYING", --1
[4] = "SHOOTER", --2
[8] = "TWO_HEX_ATTACK_BREATH", --3
-- [16] = "alive", --4
[32] = "CATAPULT", --5
[64] = "SIEGE_WEAPON", --6
[128] = "KING1", --7
[256] = "KING2", --8
[512] = "KING3", --9
[1024] = "MIND_IMMUNITY", --10
--[2048] = "laser shot", --11
[4096] = "NO_MELEE_PENALTY", --12
-- [8192] - unused --13
[16384] = "FIRE_IMMUNITY", --14
[32768] = "ADDITIONAL_ATTACK", -- val=1 --15
[65536] = "NO_RETALIATION", --16
[131072] = "NO_MORALE", --17
[262144] = "UNDEAD", --18
[524288] = "ATTACKS_ALL_ADJACENT", --19
-- [1048576] - AOE spell-like attack --20
-- [2097152] - war machine? --21
-- [4194304] = "summoned", --22
-- [8388608] = "cloned", --23
}
local FLAGS = {}
for k, v in pairs(FLAG_NAMES) do
local bonusType = Bonus[v]
assert(bonusType ~= nil, "Invalid Bonus type: "..v)
FLAGS[k] = bonusType
end
local FLAGS_REV = {}
for mask, bonusType in pairs(FLAGS) do
FLAGS_REV[bonusType] = mask
end
function MA:X(x, creatureIndex, flagsMask)
local creatureIndex = checkCreatureIndex(creatureIndex)
local creature = creatureByIndex(creatureIndex)
local creatureBonuses = creature:accessBonuses()
local all = creatureBonuses:getBonuses()
local currentMask = 0
local toRemove = {}
do
local idx = 1
local bonus = all[idx]
while bonus do
local bonusType = bonus:getType()
local mask = FLAGS_REV[bonusType]
if mask ~= nil then
if flagsMask ~= nil then
if bit.band(mask, flagsMask) == 0 then
table.insert(toRemove, bonus:toJsonNode())
end
end
currentMask = bit.bor(currentMask, mask)
end
idx = idx + 1
bonus = all[idx]
end
end
if flagsMask == nil then
local ret = currentMask
if creature:isDoubleWide() then
ret = bit.bor(ret, 1)
end
return nil, ret
else
flagsMask = tonumber(flagsMask)
local hasChanges = false
local packData = {config = {}}
local reqDoubleWide = bit.band(flagsMask, 1) > 0
if reqDoubleWide ~= creature:isDoubleWide() then
packData.config.doubleWide, hasChanges = reqDoubleWide, true
end
local toAdd = {}
for mask, name in pairs(FLAG_NAMES) do
if (bit.band(currentMask, mask) == 0) and (bit.band(flagsMask, mask) ~= 0) then
local bonus =
{
duration = {[1] = "PERMANENT"},
source = "CREATURE_ABILITY",
type = name
}
--special case
if name == "ADDITIONAL_ATTACK" then
bonus.val = 1
end
table.insert(toAdd, bonus)
end
end
hasChanges = hasChanges or #toAdd > 0 or #toRemove > 0
if hasChanges then
packData.bonuses = {toAdd = toAdd, toRemove = toRemove}
sendChanges(creatureIndex, packData)
end
end
end
return MA

36
scripts/lib/erm/MF.lua Normal file
View File

@@ -0,0 +1,36 @@
require("battle.Unit")
local ReceiverBase = require("core:erm.ReceiverBase")
local MF = ReceiverBase:new()
function MF:new(ERM)
return ReceiverBase.new(self,{ERM = ERM})
end
function MF:D(x)
return self.ERM.activeEvent:getInitalDamage()
end
function MF:E(x, ...)
error("!!MF:E is not implemented")
end
function MF:F(x, p1)
if p1 then
self.ERM.activeEvent:setDamage(p1)
return nil
else
return self.ERM.activeEvent:getDamage()
end
end
function MF:N(x)
return self.ERM.activeEvent:getTarget():unitId()
end
function MF:W(x, ...)
error("!!MF:W is not implemented")
end
return MF

15
scripts/lib/erm/MF_T.lua Normal file
View File

@@ -0,0 +1,15 @@
local TriggerBase = require("core:erm.TriggerBase")
local ApplyDamage = require("events.ApplyDamage")
local eventBus = EVENT_BUS;
local trigger = TriggerBase:new()
function trigger:new(o)
o = TriggerBase.new(self, o)
o.sub = ApplyDamage.subscribeBefore(EVENT_BUS, function(event)
o:call(event)
end)
return o
end
return trigger

109
scripts/lib/erm/OW.lua Normal file
View File

@@ -0,0 +1,109 @@
local ReceiverBase = require("core:erm.ReceiverBase")
local GAME = GAME
local SetResources = require("netpacks.SetResources")
local SERVER = SERVER
local OW = ReceiverBase:new()
function OW:new(ERM)
return ReceiverBase.new(self,{ERM = ERM})
end
function OW:A(x, ...)
logError("!!OW:A is not implemented")
end
function OW:C(x, p)
if p1 == nil then
return GAME:getCurrentPlayer()
else
error("Setting current player in not allowed")
end
end
function OW:D(x, ...)
logError("!!OW:D is not implemented")
end
function OW:G(x, ...)
logError("!!OW:G is not implemented")
end
function OW:H(x, ...)
logError("!!OW:H is not implemented")
end
function OW:I(x, ...)
logError("!!OW:I is not implemented")
end
function OW:K(x, ...)
logError("!!OW:K is not implemented")
end
function OW:N(x, ...)
logError("!!OW:N is not implemented")
end
function OW:O(x, ...)
logError("!!OW:O is not implemented")
end
function OW:R(x, player, typ, quantity)
local p = GAME:getPlayer(player)
assert(p, "Player ".. tostring(player).." not accessible")
if quantity == nil then
return nil, nil, p:getResourceAmount(typ)
elseif type(quantity) == "table" then
local hasD = false
local s = ''
local v
for n, k in ipairs(quantity) do
if k == "d" then
hasD = true
else
v = k
end
end
if not hasD then
error ("OW:R Special variable expected ".. v .." found")
end
if type(v) ~= "number" then
error ("OW:R Numeric quantity expected".. tostring(v) .." found")
end
quantity = v
local pack = SetResources.new()
pack:setPlayer(player)
pack:setAbs(false)
pack:setAmount(typ, quantity)
SERVER:commitPackage(pack)
else
local pack = SetResources.new()
pack:setPlayer(player)
pack:setAbs(true)
pack:setAmount(typ, quantity)
SERVER:commitPackage(pack)
end
end
function OW:S(x, ...)
logError("!!OW:S is not implemented")
end
function OW:T(x, ...)
logError("!!OW:T is not implemented")
end
function OW:V(x, ...)
logError("!!OW:V is not implemented")
end
function OW:W(x, ...)
logError("!!OW:W is not implemented")
end
return OW

5
scripts/lib/erm/PI_T.lua Normal file
View File

@@ -0,0 +1,5 @@
local TriggerBase = require("core:erm.TriggerBase")
local trigger = TriggerBase:new()
return trigger

View File

@@ -0,0 +1,18 @@
local ReceiverBase = {}
function ReceiverBase:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function ReceiverBase:p1Dispatcher(code)
self[code] = function(self, x, ...)
local N = select(1, ...)
return nil, self[code..tostring(N)](self, x, select(2, ...))
end
end
return ReceiverBase

57
scripts/lib/erm/TM.lua Normal file
View File

@@ -0,0 +1,57 @@
local ReceiverBase = require("core:erm.ReceiverBase")
local TM = ReceiverBase:new()
local bit = bit
local band, bor, bxor = bit.band, bit.bor, bit.bxor
local ALL_PLAYERS = 0xFF
local DATA = DATA
DATA.ERM.timers = DATA.ERM.timers or {}
local timers = DATA.ERM.timers
local function newTimer(timerId)
return
{
id = timerId,
dayFirst = 1,
dayLast = 1,
interval = 0,
players = 0
}
end
local function getTimer(timerId)
timerId = tostring(timerId)
timers[timerId] = timers[timerId] or newTimer(timerId)
return timers[timerId]
end
function TM:new(ERM, timerId)
assert(timerId ~= nil, "!!TM requires timer identifier")
return ReceiverBase.new(self,
{
timerId = timerId,
timer = getTimer(timerId),
ERM = ERM
})
end
function TM:D(x, playerMask)
--disable by mask
self.timer.players = band(self.timer.players, bnot(playerMask), ALL_PLAYERS)
end
function TM:E(x, playerMask)
--enable by mask
self.timer.players = bor(self.timer.players, playerMask)
end
function TM:S(x, dayFirst, dayLast, interval, playerMask)
local t = self.timer
t.dayFirst = dayFirst
t.dayLast = dayLast
t.interval = interval
t.players = playerMask
end
return TM

51
scripts/lib/erm/TM_T.lua Normal file
View File

@@ -0,0 +1,51 @@
local TriggerBase = require("core:erm.TriggerBase")
local PlayerGotTurn = require("events.PlayerGotTurn")
local bit = bit
local band, bor = bit.band, bit.bor
local lshift, rshift = bit.lshift, bit.rshift
local TM_T = TriggerBase:new()
function TM_T:new(o)
o = TriggerBase.new(self, o)
o.sub = PlayerGotTurn.subscribeAfter(EVENT_BUS, function(event)
o:update(event)
end)
o.timerId = tostring(o.id[1])
return o
end
function TM_T:update(event)
local timerData = DATA.ERM.timers[self.timerId]
if not timerData then
error("Timer" ..self.timerId .." is not set")
end
local today = self.ERM.Q.c -- this assumes that ERM updates before this
local activePlayer = event:getPlayer()
assert(type(activePlayer) == "number")
--TODO: special player constants
if activePlayer < 0 or activePlayer > 8 then
return
end
if band(timerData.players, lshift(1, activePlayer)) == 0 then
return
end
if (today < timerData.dayFirst) or ((today > timerData.dayLast) and (timerData.dayLast ~= 0)) then
return
end
--
local d = today - timerData.dayFirst
--
if d % timerData.interval == 0 then
self:call(event)
end
end
return TM_T

View File

@@ -0,0 +1,37 @@
local TriggerBase = {}
function TriggerBase:new(o)
o = o or {}
o.id = o.id or {}
o.fn = o.fn or {}
o.y = o.y or {}
o.e = o.e or {}
setmetatable(o, self)
self.__index = self
return o
end
function TriggerBase:call(event)
self.ERM.activeEvent = event
self.ERM.activeTrigger = self
for _, fn in ipairs(self.fn) do
fn(self.e, self.y)
end
self.ERM.activeTrigger = nil
self.ERM.activeEvent = nil
end
function TriggerBase:addHandler(fn)
table.insert(self.fn, fn)
end
function TriggerBase:checkSub(sub, sub_name)
if type(sub) == "string" then
error(sub_name .. " subscription failed: "..sub)
elseif type(sub) ~= "userdata" then
error(sub_name .. " subscription failed")
end
end
return TriggerBase

58
scripts/lib/erm/UN.lua Normal file
View File

@@ -0,0 +1,58 @@
local ReceiverBase = require("core:erm.ReceiverBase")
local Metatype = require ("core:Metatype")
local EntitiesChanged = require("netpacks.EntitiesChanged")
local SERVICES = SERVICES
local creatures = SERVICES:creatures()
local SERVER = SERVER
local function sendChanges(metatype, index, data)
local pack = EntitiesChanged.new()
pack:update(metatype, index, data)
SERVER:commitPackage(pack)
end
local UN = ReceiverBase:new()
function UN:new(ERM)
return ReceiverBase.new(self,{ERM=ERM})
end
UN:p1Dispatcher("G")
function UN:G0(x, ...)
error ("UN:G0 is not implemented")
end
function UN:G1(x, creatureIndex, N, v)
return nil, nil, (self["G1"..tostring(N)](self, x, creatureIndex, v))
end
function UN:G10(x, creatureIndex, v)
if v == nil then
return creatures:getByIndex(creatureIndex):getSingularName()
else
v = self.ERM:getZVar(v)
local packData = {config = {name={singular=v}}}
sendChanges(Metatype.CREATURE, creatureIndex, packData)
end
end
function UN:G11(x, creatureIndex, v)
if v == nil then
return creatures:getByIndex(creatureIndex):getPluralName()
else
v = self.ERM:getZVar(v)
local packData = {config = {name={plural=v}}}
sendChanges(Metatype.CREATURE, creatureIndex, packData)
end
end
function UN:G12(x, creatureIndex, v)
error ("UN:G1/*/2/* is not implemented")
end
function UN:G2(x, ...)
error ("UN:G2 is not implemented")
end
return UN

22
scripts/lib/erm/VR.lua Normal file
View File

@@ -0,0 +1,22 @@
local ReceiverBase = require("core:erm.ReceiverBase")
local VR = ReceiverBase:new()
function VR:new(ERM, v)
assert(v ~= nil, "!!VR requires variable identifier")
return ReceiverBase.new(self, {v=v, ERM=ERM})
end
local match = string.match
local function trim(s)
return match(s,'^()%s*$') and '' or match(s,'^%s*(.*%S)')
end
function VR:H(flagIndex)
local v = trim(self.v)
self.ERM.F[flagIndex] = v ~= ''
end
return VR

375
scripts/lib/verm.lua Normal file
View File

@@ -0,0 +1,375 @@
local _G=_G
local ipairs=ipairs
local select=select
local pairs=pairs
local type = type
local unpack=unpack
local logError = logError
local DATA = DATA
--/////////////////////////
local function table_print (tt, done)
done = done or {}
if type(tt) == "table" then
local sb = {}
table.insert(sb, "{");
for key, value in pairs (tt) do
if type (value) == "table" and not done [value] then
done [value] = true
table.insert(sb, key .. ":{");
table.insert(sb, table_print (value, done))
table.insert(sb, "}");
else
table.insert(sb, string.format(
"%s:\"%s\"", tostring (key), tostring(value)))
end
table.insert(sb, ",");
end
table.insert(sb, "}");
m = getmetatable(tt);
if m and m.__index then
table.insert(sb, table_print (m.__index, done));
end
return table.concat(sb)
else
return tt .. ""
end
end
local function to_string( tbl )
if "nil" == type( tbl ) then
return tostring(nil)
elseif "table" == type( tbl ) then
return table_print(tbl)
elseif "string" == type( tbl ) then
return tbl
else
return tostring(tbl)
end
end
--/////////////////////////
local VERM = {}
local function createEnv(parent, current)
return setmetatable(
current or {},
{
__parent = parent,
__index = parent,
__newindex = function(t, k ,v)
if type(k) ~= "string" then
error("String key for env. table required, but got:"..to_string(k))
end
local function setOnFirstHit(t, k, v)
local vv = rawget(t, k)
if vv~= nil then rawset(t, k, v); return true end
local m = getmetatable(t)
if not m then return false end --assume top
local p = m.__parent
if not p then
return false
else
return setOnFirstHit(p, k, v)
end
end
if not setOnFirstHit(t, k, v) then
rawset(t, k, v)
end
end
}
)
end
local function isNIL(v)
return (type(v) == "table") and (next(v) == nil)
end
local function prognForm(e, ...)
--eval each argument, return last result
local argc = select('#',...)
if argc == 0 then return {} end
for n = 1, argc - 1 do
VERM:eval(e, (select(n,...)))
end
return VERM:eval(e, (select(argc,...)))
end
local function lambdaOrMacro(e, isMacro, args, ...)
--TODO: get rid of pack-unpack
local body = {...}
local oldEnv = e
local ret = function(e, ...)
-- we need a function that have parameters with names from `args` table
-- pack parameters from '...' and bind to new environment
local newEnv = createEnv(oldEnv, {})
for i, v in ipairs(args) do
local p = select(i,...)
if isMacro then
newEnv[v] = p
else
newEnv[v] = VERM:evalValue(e, p)
end
end
if isMacro then
local buffer = {}
for _, v in ipairs(body) do
table.insert(buffer, (VERM:eval(newEnv, v)))
end
return prognForm(newEnv, unpack(buffer))
else
return prognForm(newEnv, unpack(body))
end
end
return ret
end
local function lambdaForm(e, args, ...)
return lambdaOrMacro(e, false, args, ...)
end
local function defunForm(e, name, args, ...)
local ret = lambdaOrMacro(e, false, args, ...)
e[name] = ret
return ret
end
local function defmacroForm(e, name, args, ...)
local ret = lambdaOrMacro(e, true, args, ...)
e[name] = ret
return ret
end
local function backquoteEval(e, v)
if isNIL(v) then
return v
elseif type(v) == "table" then
local car = v[1]
if car == "," then
return VERM:evalValue(e, v[2])
else
local ret = {}
for _, v in ipairs(v) do
table.insert(ret, (backquoteEval(e, v)))
end
return ret
end
else
return v
end
end
local specialForms =
{
["<"] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
if lhs < rhs then
return lhs
else
return {}
end
end,
["<="] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
if lhs <= rhs then
return lhs
else
return {}
end
end,
[">"] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
if lhs > rhs then
return lhs
else
return {}
end
end,
[">="] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
if lhs >= rhs then
return lhs
else
return {}
end
end,
["="] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
if lhs == rhs then
return lhs
else
return {}
end
end,
["+"] = function(e, ...)
local ret = 0
for n=1,select('#',...) do
local v = VERM:evalValue(e, (select(n,...)))
ret = ret + v
end
return ret
end,
["*"] = function(e, ...)
local ret = 1
for n=1,select('#',...) do
local v = VERM:evalValue(e, (select(n,...)))
ret = ret * v
end
return ret
end,
["-"] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
return lhs - rhs
end,
["/"] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
return lhs / rhs
end,
["%"] = function(e, lhs, rhs)
lhs = VERM:evalValue(e, lhs)
rhs = VERM:evalValue(e, rhs)
return lhs % rhs
end,
-- ["comma-unlist"] = function(e, ...) end,
["`"] = backquoteEval,
-- ["get-func"] = function(e, ...) end,
["'"] = function(e, v)
return v
end,
["if"] = function(e, cond, v1, v2)
cond = VERM:evalValue(e, cond)
if isNIL(cond) then
return VERM:evalValue(e, v2)
else
return VERM:evalValue(e, v1)
end
end,
-- ["set"] = function(e, ...) end,
-- ["setf"] = function(e, ...) end,
["setq"] = function(e, name, value)
e[name] = VERM:evalValue(e, value)
end,
["lambda"] = lambdaForm,
["defun"] = defunForm,
["progn"] = prognForm,
["defmacro"] = defmacroForm,
["do"] = function(e, cond, body)
local c = VERM:eval(e, cond)
while not isNIL(c) do
VERM:eval(e, body)
c = VERM:eval(e, cond)
end
return {}
end,
["car"] = function(e, list)
list = VERM:eval(e, list)
return list[1] or {}
end,
["cdr"] = function(e, list)
list = VERM:eval(e, list)
local ret = {}
for i, v in ipairs(list) do
if i > 1 then
table.insert(ret, v)
end
end
return ret
end,
["list"] = function(e, ...)
local ret = {}
for n=1,select('#',...) do
local v = VERM:evalValue(e, (select(n,...)))
table.insert(ret, v)
end
return ret
end,
["setq-erm"] = function(e, var, varIndex, value)
local v = VERM:evalValue(e, value)
DATA.ERM[var][tostring(VERM:evalValue(e, varIndex))] = v
return v
end,
}
function VERM:evalValue(e, v)
if isNIL(v) then
return v
elseif type(v) == "table" then
return self:eval(e, v)
elseif type(v) == "string" then
return e[v]
elseif type(v) == "function" then
error("evalValue do not accept functions")
else
return v
end
end
function VERM:eval(e, t)
if type(t) ~= "table" then
logError("Not valid form: ".. to_string(t))
return {}
end
local car = t[1]
local origCar = car
if type(car) == "string" then
car = e[car]
end
if type(car) == "table" then
car = self:eval(e, car)
end
if type(car) == "function" then
return car(e, unpack(t,2))
else
logError(to_string(t) .. " is not callable. Car()="..to_string(car))
logError("Env:"..to_string(e))
return {}
end
end
function VERM:E(line)
self:eval(self.topEnv, line)
end
VERM.topEnv = createEnv(specialForms)
return VERM