mirror of
https://github.com/ZDoom/raze-gles.git
synced 2024-12-25 03:00:46 +00:00
Lunatic translator: sprite list commands, findnear*, protect player[].ftq.
git-svn-id: https://svn.eduke32.com/eduke32@3498 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
parent
719efefbf0
commit
2d61216758
8 changed files with 164 additions and 92 deletions
|
@ -1,6 +1,7 @@
|
|||
-- Bound-checking functions for engine and game "things".
|
||||
|
||||
local ffiC = require("ffi").C
|
||||
local con_lang = require("con_lang")
|
||||
|
||||
local bcheck = {}
|
||||
|
||||
|
@ -71,5 +72,15 @@ function bcheck.level_idx(level)
|
|||
end
|
||||
end
|
||||
|
||||
function bcheck.quote_idx(qnum)
|
||||
if (qnum >= con_lang.MAXQUOTES+0ULL) then
|
||||
error("invalid quote number "..qnum, 3)
|
||||
end
|
||||
|
||||
if (ffiC.ScriptQuotes[qnum] == nil) then
|
||||
error("null quote "..qnum, 3)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return bcheck
|
||||
|
|
|
@ -516,9 +516,9 @@ local PlayerLabels = {
|
|||
airleft = PL".airleft",
|
||||
|
||||
fta = PL".fta",
|
||||
ftq = PL".ftq",
|
||||
access_wallnum = PL".access_wallnum",
|
||||
access_spritenum = PL".access_spritenum",
|
||||
ftq = { PL".ftq", PL":set_ftq(%%s)" },
|
||||
access_wallnum = { PL".access_wallnum" },
|
||||
access_spritenum = { PL".access_spritenum" },
|
||||
|
||||
got_access = PL".got_access",
|
||||
weapon_ang = PL".weapon_ang",
|
||||
|
|
|
@ -329,6 +329,10 @@ end
|
|||
|
||||
--- player/actor/sprite searching functions ---
|
||||
|
||||
local xmath = require("xmath")
|
||||
local abs = math.abs
|
||||
local dist, ldist = xmath.dist, xmath.ldist
|
||||
|
||||
local function A_FP_ManhattanDist(ps, spr)
|
||||
return (ps.pos - spr^(28*256)):blen1()
|
||||
end
|
||||
|
@ -339,6 +343,48 @@ function _findplayer(ps, spritenum)
|
|||
return 0, A_FP_ManhattanDist(ps, sprite[spritenum])
|
||||
end
|
||||
|
||||
local FN_STATNUMS = {
|
||||
[false] = { con_lang.STAT.STAT_ACTOR },
|
||||
[true] = {},
|
||||
}
|
||||
|
||||
-- TODO: Python-like range() and xrange()?
|
||||
for i=0,ffiC.MAXSTATUS-1 do
|
||||
FN_STATNUMS[true][i+1] = ffiC.MAXSTATUS-1-i
|
||||
end
|
||||
|
||||
local FN_DISTFUNC = {
|
||||
d2 = function(s1, s2, d)
|
||||
return (xmath.ldist(s1, s2) < d)
|
||||
end,
|
||||
|
||||
d3 = function(s1, s2, d)
|
||||
return (xmath.dist(s1, s2) < d)
|
||||
end,
|
||||
|
||||
z = function(s1, s2, d, zd)
|
||||
return (xmath.ldist(s1, s2) < d and abs(s1.z-s2.z) < zd)
|
||||
end,
|
||||
}
|
||||
|
||||
function _findnear(spritenum, allspritesp, distkind, picnum, maxdist, maxzdist)
|
||||
local statnums = FN_STATNUMS[allspritesp]
|
||||
local distfunc = FN_DISTFUNC[distkind]
|
||||
local spr = sprite[spritenum]
|
||||
|
||||
for st=1,#statnums do
|
||||
for i in spritesofstat(st) do
|
||||
if (i ~= spritenum and sprite[i].picnum==picnum) then
|
||||
if (distfunc(spr, sprite[i], maxdist, maxzdist)) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
|
||||
---=== Weapon stuff ===---
|
||||
|
||||
|
@ -374,31 +420,21 @@ end
|
|||
|
||||
local MAXQUOTES = con_lang.MAXQUOTES
|
||||
|
||||
local function check_quote_idx(qnum)
|
||||
if (qnum >= MAXQUOTES+0ULL) then
|
||||
error("invalid quote number "..qnum, 3)
|
||||
end
|
||||
|
||||
if (ffiC.ScriptQuotes[qnum] == nil) then
|
||||
error("null quote "..qnum, 3)
|
||||
end
|
||||
end
|
||||
|
||||
function _definequote(qnum, quotestr)
|
||||
check_quote_idx(qnum)
|
||||
bcheck.quote_idx(qnum)
|
||||
assert(type(quotestr)=="string")
|
||||
ffiC.C_DefineQuote(qnum, quotestr)
|
||||
return (#quotestr >= con_lang.MAXQUOTELEN)
|
||||
end
|
||||
|
||||
function _quote(pli, qnum)
|
||||
check_quote_idx(qnum)
|
||||
bcheck.quote_idx(qnum)
|
||||
check_player_idx(pli)
|
||||
ffiC.P_DoQuote(qnum+MAXQUOTES, ffiC.g_player[pli].ps)
|
||||
end
|
||||
|
||||
function _echo(qnum)
|
||||
check_quote_idx(qnum)
|
||||
bcheck.quote_idx(qnum)
|
||||
-- XXX: ugly round-trip
|
||||
print(ffi.string(ffiC.ScriptQuotes[qnum]))
|
||||
end
|
||||
|
@ -771,14 +807,12 @@ function _awayfromwall(spr, d)
|
|||
return true
|
||||
end
|
||||
|
||||
local xmath = require("xmath")
|
||||
|
||||
local function cossinb(bang)
|
||||
return xmath.cosb(bang), xmath.sinb(bang)
|
||||
end
|
||||
|
||||
local function manhatdist(v1, v2)
|
||||
return math.abs(v1.x-v2.x) + math.abs(v1.y-v2.y)
|
||||
return abs(v1.x-v2.x) + abs(v1.y-v2.y)
|
||||
end
|
||||
|
||||
-- "otherspr" is either player or holoduke sprite
|
||||
|
@ -950,8 +984,8 @@ function _angdiffabs(a1, a2)
|
|||
a1 = bit.band(a1, 2047)
|
||||
a2 = bit.band(a2, 2047)
|
||||
-- a1 and a2 are in [0, 2047]
|
||||
if (math.abs(a2-a1) < 1024) then
|
||||
return math.abs(a2-a1)
|
||||
if (abs(a2-a1) < 1024) then
|
||||
return abs(a2-a1)
|
||||
end
|
||||
-- |a2-a1| >= 1024
|
||||
if (a2 > 1024) then a2=a2-2048 end
|
||||
|
@ -961,7 +995,7 @@ function _angdiffabs(a1, a2)
|
|||
end
|
||||
|
||||
function _angdiffabs(a1, a2)
|
||||
return math.abs(_angdiff(a1, a2))
|
||||
return abs(_angdiff(a1, a2))
|
||||
end
|
||||
|
||||
function _angtotarget(aci)
|
||||
|
|
|
@ -199,7 +199,8 @@ __attribute__((packed)) struct {
|
|||
int16_t horiz, horizoff, ohoriz, ohorizoff;
|
||||
const int16_t newowner;
|
||||
int16_t jumping_counter, airleft;
|
||||
int16_t fta, ftq, access_wallnum, access_spritenum;
|
||||
int16_t fta;
|
||||
const int16_t ftq, access_wallnum, access_spritenum;
|
||||
int16_t got_access, weapon_ang, visibility;
|
||||
int16_t somethingonplayer, on_crane;
|
||||
const int16_t i;
|
||||
|
@ -911,6 +912,11 @@ local player_mt = {
|
|||
p.inv_amount[inv] = amount
|
||||
end,
|
||||
|
||||
set_ftq = function(p, ftq)
|
||||
bcheck.quote_idx(ftq)
|
||||
ffi.cast(player_ptr_ct, p).ftq = ftq
|
||||
end,
|
||||
|
||||
set_customexitsound = function(p, soundnum)
|
||||
if (soundnum >= con_lang.MAXSOUNDS+0ULL) then
|
||||
error("Invalid sound number "..soundnum, 2)
|
||||
|
@ -1047,6 +1053,15 @@ function gv_access._currentMenu()
|
|||
return ffiC.g_currentMenu
|
||||
end
|
||||
|
||||
function gv_access._set_guniqhudid(id)
|
||||
local MAXUNIQHUDID = 256 -- KEEPINSYNC build.h
|
||||
if (id >= MAXUNIQHUDID+0ULL) then
|
||||
error("invalid unique HUD ID "..id)
|
||||
end
|
||||
ffiC.guniqhudid = id
|
||||
end
|
||||
|
||||
-- TODO: make return 1-based index
|
||||
function gv_access.currentEpisode()
|
||||
return ffiC.ud.volume_number
|
||||
end
|
||||
|
|
|
@ -228,6 +228,7 @@ const int32_t windowx1, windowy1, windowx2, windowy2;
|
|||
decl[[
|
||||
int32_t yxaspect, viewingrange;
|
||||
int32_t spritesortcnt;
|
||||
int32_t guniqhudid;
|
||||
const int32_t rendmode;
|
||||
const int16_t headspritesect[MAXSECTORS+1], headspritestat[MAXSTATUS+1];
|
||||
const int16_t prevspritesect[MAXSPRITES], prevspritestat[MAXSPRITES];
|
||||
|
@ -511,6 +512,24 @@ end
|
|||
|
||||
---- indirect C array access ----
|
||||
|
||||
-- create a safe indirection for an ffi.C array
|
||||
function creategtab(ctab, maxidx, name)
|
||||
local tab = {}
|
||||
local tmpmt = {
|
||||
__index = function(tab, key)
|
||||
if (key>=0 and key < maxidx) then
|
||||
return ctab[key]
|
||||
end
|
||||
error('out-of-bounds '..name..' read access', 2)
|
||||
end,
|
||||
__newindex = function()
|
||||
error('cannot write directly to '..name, 2)
|
||||
end,
|
||||
}
|
||||
|
||||
return setmtonce(tab, tmpmt)
|
||||
end
|
||||
|
||||
-- Construct const struct from table
|
||||
function conststruct(tab)
|
||||
local strtab = { "const struct { int32_t " }
|
||||
|
@ -554,6 +573,15 @@ static_members.sprite.CSTAT = conststruct
|
|||
TRANSLUCENT_BOTH_BITS = 512+2,
|
||||
}
|
||||
|
||||
local sms = static_members.sprite
|
||||
sms._headspritesect = creategtab(ffiC.headspritesect, ffiC.MAXSECTORS, 'headspritesect[]')
|
||||
-- NOTE: don't allow freelist access
|
||||
sms._headspritestat = creategtab(ffiC.headspritestat, ffiC.MAXSTATUS, 'headspritestat[]')
|
||||
sms._nextspritesect = creategtab(ffiC.nextspritesect, ffiC.MAXSPRITES, 'nextspritesect[]')
|
||||
sms._nextspritestat = creategtab(ffiC.nextspritestat, ffiC.MAXSPRITES, 'nextspritestat[]')
|
||||
sms._prevspritesect = creategtab(ffiC.prevspritesect, ffiC.MAXSPRITES, 'prevspritesect[]')
|
||||
sms._prevspritestat = creategtab(ffiC.prevspritestat, ffiC.MAXSPRITES, 'prevspritestat[]')
|
||||
|
||||
function static_members.sprite.changesect(spritenum, sectnum)
|
||||
check_sprite_idx(spritenum)
|
||||
check_sector_idx(sectnum)
|
||||
|
@ -611,24 +639,6 @@ local atsprite_mt = {
|
|||
__newindex = function() error('cannot write directly to atsprite[]', 2) end,
|
||||
}
|
||||
|
||||
-- create a safe indirection for an ffi.C array
|
||||
function creategtab(ctab, maxidx, name)
|
||||
local tab = {}
|
||||
local tmpmt = {
|
||||
__index = function(tab, key)
|
||||
if (key>=0 and key < maxidx) then
|
||||
return ctab[key]
|
||||
end
|
||||
error('out-of-bounds '..name..' read access', 2)
|
||||
end,
|
||||
__newindex = function()
|
||||
error('cannot write directly to '..name, 2)
|
||||
end,
|
||||
}
|
||||
|
||||
return setmtonce(tab, tmpmt)
|
||||
end
|
||||
|
||||
|
||||
local vars_to_ignore = {}
|
||||
for varname,_ in pairs(getfenv(1)) do
|
||||
|
@ -646,14 +656,6 @@ sprite = setmtonce({}, sprite_mt)
|
|||
spriteext = creategtab(ffiC.spriteext, ffiC.MAXSPRITES, 'spriteext[]')
|
||||
atsprite = setmtonce({}, atsprite_mt)
|
||||
|
||||
headspritesect = creategtab(ffiC.headspritesect, ffiC.MAXSECTORS, 'headspritesect[]')
|
||||
-- TODO: allow sprite freelist access via the status list for CON compatibility?
|
||||
headspritestat = creategtab(ffiC.headspritestat, ffiC.MAXSTATUS, 'headspritestat[]')
|
||||
nextspritesect = creategtab(ffiC.nextspritesect, ffiC.MAXSPRITES, 'nextspritesect[]')
|
||||
nextspritestat = creategtab(ffiC.nextspritestat, ffiC.MAXSPRITES, 'nextspritestat[]')
|
||||
prevspritesect = creategtab(ffiC.prevspritesect, ffiC.MAXSPRITES, 'prevspritesect[]')
|
||||
prevspritestat = creategtab(ffiC.prevspritestat, ffiC.MAXSPRITES, 'prevspritestat[]')
|
||||
|
||||
local function iter_wallsofsec(endwall, w)
|
||||
w = w+1
|
||||
if (w < endwall) then
|
||||
|
|
|
@ -29,8 +29,6 @@ local ffi, ffiC
|
|||
|
||||
if (string.dump) then
|
||||
bit = require("bit")
|
||||
-- For Rio Lua:
|
||||
-- bit = { bor=function() return 0 end }
|
||||
require("strict")
|
||||
else
|
||||
bit = require("bit")
|
||||
|
@ -1492,12 +1490,14 @@ local Cinner = {
|
|||
/ handle.NYI, -- will never be
|
||||
cmenu = cmd(R)
|
||||
/ handle.NYI,
|
||||
checkavailweapon = cmd(R),
|
||||
checkavailinven = cmd(R),
|
||||
guniqhudid = cmd(R),
|
||||
checkavailweapon = cmd(R) -- THISACTOR
|
||||
/ handle.NYI,
|
||||
checkavailinven = cmd(R) -- THISACTOR
|
||||
/ handle.NYI,
|
||||
guniqhudid = cmd(R)
|
||||
/ "_gv._set_guniqhudid(%1)",
|
||||
savegamevar = cmd(R),
|
||||
readgamevar = cmd(R),
|
||||
userquote = cmd(R),
|
||||
echo = cmd(R)
|
||||
/ "_con._echo(%1)",
|
||||
activatecheat = cmd(R)
|
||||
|
@ -1579,8 +1579,6 @@ local Cinner = {
|
|||
/ "_con._spawnmany(_aci,1233,%1)", -- TODO: dyntile
|
||||
paper = cmd(D)
|
||||
/ "_con._spawnmany(_aci,4460,%1)", -- TODO: dyntile
|
||||
quote = cmd(D)
|
||||
/ "_con._quote(_pli,%1)",
|
||||
savenn = cmd(D),
|
||||
save = cmd(D),
|
||||
sleeptime = cmd(D)
|
||||
|
@ -1630,7 +1628,7 @@ local Cinner = {
|
|||
tip = cmd()
|
||||
/ PLS".tipincs=26",
|
||||
tossweapon = cmd()
|
||||
/ "", -- TODO
|
||||
/ "", -- TODO_MP
|
||||
wackplayer = cmd()
|
||||
/ PLS":wack()",
|
||||
|
||||
|
@ -1638,19 +1636,31 @@ local Cinner = {
|
|||
findplayer = cmd(W)
|
||||
/ CSV".RETURN,%1=_con._findplayer(_pli,_aci)", -- player index, distance
|
||||
findotherplayer = cmd(W)
|
||||
/ CSV".RETURN,%1=0,0x7fffffff", -- TODO: MP case
|
||||
findnearspritezvar = cmd(D,R,R,W),
|
||||
findnearspritez = cmd(D,D,D,W),
|
||||
findnearsprite3dvar = cmd(D,R,W),
|
||||
findnearsprite3d = cmd(D,D,W),
|
||||
findnearspritevar = cmd(D,R,W),
|
||||
findnearsprite = cmd(D,D,W),
|
||||
findnearactorzvar = cmd(D,R,R,W),
|
||||
findnearactorz = cmd(D,D,D,W),
|
||||
findnearactor3dvar = cmd(D,R,W),
|
||||
findnearactor3d = cmd(D,D,W),
|
||||
findnearactorvar = cmd(D,R,W),
|
||||
findnearactor = cmd(D,D,W),
|
||||
/ CSV".RETURN,%1=0,0x7fffffff", -- TODO_MP
|
||||
findnearspritezvar = cmd(D,R,R,W)
|
||||
/ "%4=_con._findnear(_aci,true,'z',%1,%2,%3)",
|
||||
findnearspritez = cmd(D,D,D,W)
|
||||
/ "%4=_con._findnear(_aci,true,'z',%1,%2,%3)",
|
||||
findnearsprite3dvar = cmd(D,R,W)
|
||||
/ "%3=_con._findnear(_aci,true,'d3',%1,%2)",
|
||||
findnearsprite3d = cmd(D,D,W)
|
||||
/ "%3=_con._findnear(_aci,true,'d3',%1,%2)",
|
||||
findnearspritevar = cmd(D,R,W)
|
||||
/ "%3=_con._findnear(_aci,true,'d2',%1,%2)",
|
||||
findnearsprite = cmd(D,D,W)
|
||||
/ "%3=_con._findnear(_aci,true,'d2',%1,%2)",
|
||||
findnearactorzvar = cmd(D,R,R,W)
|
||||
/ "%4=_con._findnear(_aci,false,'z',%1,%2,%3)",
|
||||
findnearactorz = cmd(D,D,D,W)
|
||||
/ "%4=_con._findnear(_aci,false,'z',%1,%2,%3)",
|
||||
findnearactor3dvar = cmd(D,R,W)
|
||||
/ "%3=_con._findnear(_aci,false,'d3',%1,%2)",
|
||||
findnearactor3d = cmd(D,D,W)
|
||||
/ "%3=_con._findnear(_aci,false,'d3',%1,%2)",
|
||||
findnearactorvar = cmd(D,R,W)
|
||||
/ "%3=_con._findnear(_aci,false,'d2',%1,%2)",
|
||||
findnearactor = cmd(D,D,W)
|
||||
/ "%3=_con._findnear(_aci,false,'d2',%1,%2)",
|
||||
|
||||
-- quotes
|
||||
qsprintf = sp1 * tok.rvar * sp1 * tok.rvar * (sp1 * tok.rvar)^-32,
|
||||
|
@ -1660,6 +1670,11 @@ local Cinner = {
|
|||
qstrlen = cmd(R,R),
|
||||
qstrncat = cmd(R,R),
|
||||
qsubstr = cmd(R,R),
|
||||
quote = cmd(D)
|
||||
/ "_con._quote(_pli,%1)",
|
||||
userquote = cmd(R),
|
||||
getkeyname = cmd(R,R,R),
|
||||
getpname = cmd(R,R),
|
||||
|
||||
-- array stuff
|
||||
copy = sp1 * tok.identifier * arraypat * sp1 * tok.identifier * arraypat * sp1 * tok.rvar,
|
||||
|
@ -1751,12 +1766,18 @@ local Cinner = {
|
|||
loadmapstate = cmd(),
|
||||
savemapstate = cmd(),
|
||||
|
||||
headspritesect = cmd(W,R),
|
||||
headspritestat = cmd(W,R),
|
||||
nextspritesect = cmd(W,R),
|
||||
nextspritestat = cmd(W,R),
|
||||
prevspritesect = cmd(W,R),
|
||||
prevspritestat = cmd(W,R),
|
||||
headspritesect = cmd(W,R)
|
||||
/ "%1=sprite._headspritesect[%2]",
|
||||
headspritestat = cmd(W,R)
|
||||
/ "%1=sprite._headspritestat[%2]",
|
||||
nextspritesect = cmd(W,R)
|
||||
/ "%1=sprite._nextspritesect[%2]",
|
||||
nextspritestat = cmd(W,R)
|
||||
/ "%1=sprite._nextspritestat[%2]",
|
||||
prevspritesect = cmd(W,R)
|
||||
/ "%1=sprite._prevspritesect[%2]",
|
||||
prevspritestat = cmd(W,R)
|
||||
/ "%1=sprite._prevspritestat[%2]",
|
||||
|
||||
redefinequote = sp1 * tok.define * newline_term_string
|
||||
/ function(qnum, qstr) return format("_con._definequote(%d,%q)", qnum, stripws(qstr)) end,
|
||||
|
@ -1821,8 +1842,6 @@ local Cinner = {
|
|||
/ "%4=sector[%1]:floorzat(%2,%3)",
|
||||
getcurraddress = cmd(W)
|
||||
/ handle.NYI, -- will never be
|
||||
getkeyname = cmd(R,R,R),
|
||||
getpname = cmd(R,R),
|
||||
getticks = cmd(W)
|
||||
/ "%1=_gv.getticks()",
|
||||
gettimedate = cmd(W,W,W,W,W,W,W,W)
|
||||
|
@ -1868,7 +1887,7 @@ local Cif = {
|
|||
ifspritepal = cmd(D)
|
||||
/ SPS".pal==%1",
|
||||
ifgotweaponce = cmd(D)
|
||||
/ "false", -- TODO? (multiplayer only)
|
||||
/ "false", -- TODO_MP
|
||||
ifangdiffl = cmd(D)
|
||||
/ format("_con._angdiffabs(%s,%s)<=%%1", PLS".ang", SPS".ang"),
|
||||
ifsound = cmd(D)
|
||||
|
@ -1931,7 +1950,7 @@ local Cif = {
|
|||
ifnosounds = cmd()
|
||||
/ "not _con._ianysound()",
|
||||
ifmultiplayer = cmd()
|
||||
/ "false", -- TODO?
|
||||
/ "false", -- TODO_MP
|
||||
ifinwater = cmd()
|
||||
/ format("sector[%s].lotag==2", SPS".sectnum"),
|
||||
ifinspace = cmd()
|
||||
|
|
|
@ -136,10 +136,10 @@ checkfail('gv.QWE = 4', "write access forbidden")
|
|||
checkfail('sector[4] = sector[6]', "cannot write directly to sector[]")
|
||||
|
||||
-- that would be horrible...
|
||||
checkfail('nextspritesect[4] = -666', "cannot write directly to nextspritesect[]")
|
||||
checkfail('sprite._nextspritesect[4] = -666', "cannot write directly to nextspritesect[]")
|
||||
|
||||
-- we're indexing a plain array!
|
||||
checkfail('print(nextspritesect[4].whatfield)', "attempt to index a number value")
|
||||
checkfail('print(sprite._nextspritesect[4].whatfield)', "attempt to index a number value")
|
||||
|
||||
-- creating new keys forbidden... handled by LuaJit
|
||||
checkfail('wall[4].QWE = 123', "has no member named 'QWE'")
|
||||
|
|
|
@ -2032,12 +2032,6 @@ static int32_t P_DisplayTip(int32_t gs,int32_t snum)
|
|||
|
||||
p = P_GetHudPal(ps);
|
||||
|
||||
/* if(ps->access_spritenum >= 0)
|
||||
p = sprite[ps->access_spritenum].pal;
|
||||
else
|
||||
p = wall[ps->access_wallnum].pal;
|
||||
*/
|
||||
|
||||
tipy = tip_y[ps->tipincs]>>1;
|
||||
|
||||
G_DrawTileScaled(170+(g_player[snum].sync->avel>>4)-(ps->look_ang>>1),
|
||||
|
@ -2069,9 +2063,6 @@ static int32_t P_DisplayAccess(int32_t gs,int32_t snum)
|
|||
if (ps->access_spritenum >= 0)
|
||||
p = sprite[ps->access_spritenum].pal;
|
||||
|
||||
// else
|
||||
// p = wall[ps->access_wallnum].pal;
|
||||
|
||||
if ((ps->access_incs-3) > 0 && (ps->access_incs-3)>>3)
|
||||
{
|
||||
guniqhudid = 200;
|
||||
|
|
Loading…
Reference in a new issue