mirror of
https://github.com/badvision/lawless-legends.git
synced 2024-12-26 19:29:27 +00:00
Got triggers working right on 3D map, including multiple triggers per location.
This commit is contained in:
parent
375cac1459
commit
80cf3ddb03
@ -470,9 +470,9 @@ class PackPartitions
|
||||
def tile = (row && x < width) ? row[x] : null
|
||||
def flags = 0
|
||||
if ([colNum, rowNum] in locationsWithTriggers)
|
||||
flags |= 0x20
|
||||
if (tile?.@obstruction == 'true')
|
||||
flags |= 0x40
|
||||
if (tile?.@obstruction == 'true')
|
||||
flags |= 0x80
|
||||
buf.put((byte)((tile ? tileMap[tile.@id] : 0) | flags))
|
||||
}
|
||||
}
|
||||
@ -727,7 +727,7 @@ class PackPartitions
|
||||
// See if the set we're considering has room for all our tiles
|
||||
def inCommon = it.tileIds.intersect(tileIds)
|
||||
def together = it.tileIds + tileIds
|
||||
if (together.size() <= 32 && inCommon.size() > bestCommon) {
|
||||
if (together.size() <= 64 && inCommon.size() > bestCommon) {
|
||||
tileSet = it
|
||||
bestCommon = inCommon.size()
|
||||
}
|
||||
@ -762,7 +762,7 @@ class PackPartitions
|
||||
def id = tile?.@id
|
||||
if (tile && !tileMap.containsKey(id)) {
|
||||
def num = tileMap.size()+1
|
||||
assert num < 32 : "Error: Only 31 kinds of tiles are allowed on any given map."
|
||||
assert num < 64 : "Error: Only 63 kinds of tiles are allowed on any given map."
|
||||
tileMap[id] = num
|
||||
tiles[id].flip() // crazy stuff to append one buffer to another
|
||||
buf.put(tiles[id])
|
||||
@ -1748,7 +1748,9 @@ class PackPartitions
|
||||
y -= yRange[0]
|
||||
if (!triggers[y])
|
||||
triggers[y] = [:] as TreeMap
|
||||
triggers[y][x] = (idx+1) * 5 // address of function
|
||||
if (!triggers[y][x])
|
||||
triggers[y][x] = []
|
||||
triggers[y][x].add((idx+1) * 5) // address of function
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1767,10 +1769,16 @@ class PackPartitions
|
||||
// The table itself goes in the data segment.
|
||||
triggers.each { y, xs ->
|
||||
emitDataByte(y)
|
||||
emitDataByte(2 + (xs.size() * 3)) // 2 bytes for y+off, plus 3 bytes per trigger (x, adrlo, adrhi)
|
||||
xs.each { x, funcAddr ->
|
||||
emitDataByte(x)
|
||||
emitDataFixup(funcAddr)
|
||||
def size = 2 // 2 bytes for y+off
|
||||
xs.each { x, funcAddrs ->
|
||||
size += funcAddrs.size() * 3 // plus 3 bytes per trigger (x, adrlo, adrhi)
|
||||
}
|
||||
emitDataByte(size)
|
||||
xs.each { x, funcAddrs ->
|
||||
funcAddrs.each { funcAddr ->
|
||||
emitDataByte(x)
|
||||
emitDataFixup(funcAddr)
|
||||
}
|
||||
// Record a list of trigger locations for the caller's reference
|
||||
locationsWithTriggers << [x, y]
|
||||
}
|
||||
|
@ -1906,14 +1906,16 @@ doAllFixups: !zone
|
||||
; Utility routine for convenient assembly routines in PLASMA code.
|
||||
; Params: Y=number of parameters passed from PLASMA routine
|
||||
; 1. Save PLASMA's X register index to evalStk
|
||||
; 2. Switch to ROM
|
||||
; 3. Load the last parameter into A=lo, Y=hi
|
||||
; 4. Run the calling routine (X still points into evalStk for add'l params if needed)
|
||||
; 5. Switch back to LC RAM
|
||||
; 6. Restore PLASMA's X register, and advance it over the parameter(s)
|
||||
; 7. Store A=lo/Y=hi into PLASMA return value
|
||||
; 8. Return to PLASMA
|
||||
; 2. Verify X register is in the range 0-$10
|
||||
; 3. Switch to ROM
|
||||
; 4. Load the *last* parameter into A=lo, Y=hi
|
||||
; 5. Run the calling routine (X still points into evalStk for add'l params if needed)
|
||||
; 6. Switch back to LC RAM
|
||||
; 7. Restore PLASMA's X register, and advance it over the parameter(s)
|
||||
; 8. Store A=lo/Y=hi into PLASMA return value
|
||||
; 9. Return to PLASMA
|
||||
__asmPlasm: !zone
|
||||
bit setROM ; switch to ROM
|
||||
pla ; save address of calling routine, so we can call it
|
||||
clc
|
||||
adc #1
|
||||
@ -1925,9 +1927,12 @@ __asmPlasm: !zone
|
||||
dey
|
||||
sty tmp
|
||||
txa
|
||||
.add adc tmp
|
||||
cpx #$11
|
||||
bcs .badx ; X must be in range 0..$10
|
||||
.add adc tmp ; carry cleared by cpx above
|
||||
pha ; and save that
|
||||
bit setROM ; switch to ROM
|
||||
cmp #$11 ; again, X must be in range 0..$10
|
||||
bcs .badx
|
||||
lda evalStkL,x ; get last param to A=lo
|
||||
ldy evalStkH,x ; ...Y=hi
|
||||
.jsr jsr $1111 ; call the routine to do work
|
||||
@ -1940,6 +1945,16 @@ __asmPlasm: !zone
|
||||
tya
|
||||
sta evalStkH,x
|
||||
rts ; and return to PLASMA interpreter
|
||||
.badx jsr crout ; X reg ran outside valid range. Print and abort.
|
||||
lda #'X'
|
||||
jsr cout
|
||||
txa
|
||||
jsr prbyte
|
||||
jsr crout
|
||||
ldx #<+
|
||||
ldy #>+
|
||||
jmp fatalError
|
||||
+ !text $8D, "PLASMA x-reg out of range", 0
|
||||
|
||||
;------------------------------------------------------------------------------
|
||||
; Segment tables
|
||||
|
@ -277,16 +277,16 @@ FATAL_ERROR = $1F
|
||||
|
||||
;------------------------------------------------------------------------------
|
||||
; Convenience for writing assembly routines in PLASMA source
|
||||
; Macro param: number of parameters passed from PLASMA routine
|
||||
; 1. Save PLASMA's X register index
|
||||
; 2. Switch to ROM
|
||||
; 3. Load the last parameter (if any) into A=lo, Y=hi
|
||||
; 4. Run the calling routine (X still points into evalStk for add'l params if needed)
|
||||
; 5. Switch back to LC RAM
|
||||
; 6. Restore PLASMA's X register, and advance it over the parameter(s), leaving
|
||||
; space for the return value.
|
||||
; 7. Store A=lo/Y=hi into PLASMA return value
|
||||
; 8. Return to PLASMA
|
||||
; Macro param: number of parameters passed from PLASMA to the asm routine
|
||||
; 1. Save PLASMA's X register index to evalStk
|
||||
; 2. Verify X register is in the range 0-$10
|
||||
; 3. Switch to ROM
|
||||
; 4. Load the *last* parameter into A=lo, Y=hi
|
||||
; 5. Run the calling routine (X still points into evalStk for add'l params if needed)
|
||||
; 6. Switch back to LC RAM
|
||||
; 7. Restore PLASMA's X register, and advance it over the parameter(s)
|
||||
; 8. Store A=lo/Y=hi into PLASMA return value
|
||||
; 9. Return to PLASMA
|
||||
!macro asmPlasm nArgs {
|
||||
ldy #nArgs
|
||||
jsr _asmPlasm
|
||||
|
@ -91,6 +91,7 @@ word triggerTbl
|
||||
byte redraw
|
||||
byte frameLoaded = 0
|
||||
byte textDrawn = FALSE
|
||||
byte needRender = FALSE
|
||||
|
||||
word skyNum = 9
|
||||
word groundNum = 10
|
||||
@ -125,30 +126,39 @@ end
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API to call rendering engine (same API for raycaster and tile engine)
|
||||
asm initDisplay // params: mapNum, pMapData, x, y, dir
|
||||
+asmPlasm 5
|
||||
jmp $6000
|
||||
end
|
||||
asm flipToPage1 // no params
|
||||
+asmPlasm 0
|
||||
jmp $6003
|
||||
end
|
||||
asm getPos // params: @x, @y
|
||||
+asmPlasm 2
|
||||
jmp $6006
|
||||
end
|
||||
asm setPos // params: x, y
|
||||
+asmPlasm 2
|
||||
jmp $6009
|
||||
end
|
||||
asm getDir // no params; returns: dir (0-15)
|
||||
+asmPlasm 0
|
||||
jmp $600C
|
||||
end
|
||||
asm setDir // params: dir (0-15)
|
||||
+asmPlasm 1
|
||||
jmp $600F
|
||||
end
|
||||
asm advance // no params; return: 0 if same pos, 1 if new pos, 2 if new pos and scripted
|
||||
+asmPlasm 0
|
||||
jmp $6012
|
||||
end
|
||||
asm setColor // params: slot (0=sky/1=ground), color (0-15)
|
||||
+asmPlasm 2
|
||||
jmp $6015
|
||||
end
|
||||
asm render // no params
|
||||
+asmPlasm 0
|
||||
jmp $6018
|
||||
end
|
||||
|
||||
@ -156,11 +166,9 @@ end
|
||||
// Simply retrieve the X register. Used to double-check that we're not leaking PLASMA eval
|
||||
// stack entries.
|
||||
asm getXReg
|
||||
+asmPlasm 0
|
||||
txa
|
||||
dex ; make room for return value
|
||||
sta evalStkL,x
|
||||
lda #0
|
||||
sta evalStkH,x
|
||||
ldy #0
|
||||
rts
|
||||
end
|
||||
|
||||
@ -628,6 +636,7 @@ def initMap(x, y, dir)
|
||||
puts("Calling initDisplay.\n")
|
||||
initDisplay(mapNum, pMap, x, y, dir)
|
||||
puts("Back from initDisplay.\n")
|
||||
needRender = FALSE
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -700,12 +709,20 @@ end
|
||||
def moveForward()
|
||||
byte val
|
||||
val = advance()
|
||||
printf1("advance returned %d\n", val)
|
||||
if val > 0 and textDrawn
|
||||
|
||||
// If not blocked, render at the new position.
|
||||
if val > 0
|
||||
needRender = TRUE
|
||||
fin
|
||||
|
||||
// If we're on a new map tile, clear text from script(s) on the old tile.
|
||||
if val >= 2 and textDrawn
|
||||
clearWindow()
|
||||
textDrawn = FALSE
|
||||
fin
|
||||
if val == 2
|
||||
|
||||
// If there are script(s) on the new tile, run them.
|
||||
if val == 3
|
||||
checkScripts()
|
||||
fin
|
||||
end
|
||||
@ -728,16 +745,14 @@ end
|
||||
// Turn left (3D mode)
|
||||
def rotateLeft()
|
||||
adjustDir(-1)
|
||||
render()
|
||||
needRender = TRUE
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Rotate to the right (3D mode)
|
||||
def rotateRight()
|
||||
adjustDir(1)
|
||||
puts("About to call render.\n")
|
||||
render()
|
||||
puts("Back from render.\n")
|
||||
needRender = TRUE
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -788,7 +803,7 @@ def setMap(is3D, num, x, y, dir)
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
def teleport()
|
||||
def kbdTeleport()
|
||||
word x, y
|
||||
byte dir
|
||||
|
||||
@ -815,9 +830,11 @@ def teleport()
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
def oldTeleport(x, y, dir)
|
||||
printf3("Old teleport: x=%d y=%d dir=%d\n", x, y, dir)
|
||||
puts("Doing nothing for now.\n")
|
||||
def teleport(x, y, dir)
|
||||
printf3("Teleport: x=%d y=%d dir=%d\n", x, y, dir)
|
||||
setPos(x, y)
|
||||
setDir(dir)
|
||||
needRender = TRUE
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -838,6 +855,10 @@ def kbdLoop()
|
||||
func = cmdTbl[key]
|
||||
if func; func(); fin
|
||||
fin
|
||||
if needRender
|
||||
render()
|
||||
needRender = FALSE
|
||||
fin
|
||||
loop
|
||||
end
|
||||
|
||||
@ -855,10 +876,8 @@ def setScriptInfo(mapName, trigTbl)
|
||||
setWindow1()
|
||||
//displayStr(mapName)
|
||||
|
||||
// Set up for drawing other script text.
|
||||
// Back to the main text window.
|
||||
setWindow2()
|
||||
clearWindow()
|
||||
textDrawn = FALSE
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -897,7 +916,7 @@ def initCmds()
|
||||
next
|
||||
|
||||
// Commands common to both 2D and 3D
|
||||
initCmd('T', @teleport)
|
||||
initCmd('T', @kbdTeleport)
|
||||
|
||||
// Commands handled differently in 3D vs 2D
|
||||
if mapIs3D
|
||||
@ -987,7 +1006,7 @@ def setCallbacks()
|
||||
|
||||
// $312
|
||||
callbacks.18 = $4c
|
||||
callbacks:19 = @oldTeleport
|
||||
callbacks:19 = @teleport
|
||||
end
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1668,13 +1668,11 @@ calcMapOrigin: !zone
|
||||
;-------------------------------------------------------------------------------
|
||||
; Advance in current direction if not blocked.
|
||||
; Params: none
|
||||
; Return: 0 if same map tile;
|
||||
; 1 if pos is on a new map tile;
|
||||
; 2 if that new tile is also scripted
|
||||
; Return: 0 if blocked;
|
||||
; 1 if advanced but still within same map tile;
|
||||
; 2 if pos is on a new map tile;
|
||||
; 3 if that new tile is also scripted
|
||||
pl_advance: !zone
|
||||
txa
|
||||
pha ; save PLASMA eval stk pos
|
||||
bit setROM ; switch out PLASMA while we work
|
||||
lda playerDir
|
||||
asl
|
||||
asl ; shift twice: each dir is 4 bytes in table
|
||||
@ -1699,7 +1697,6 @@ pl_advance: !zone
|
||||
sta playerY
|
||||
lda playerY+1
|
||||
pha
|
||||
clc
|
||||
adc walkDirs+3,x
|
||||
sta playerY+1
|
||||
|
||||
@ -1728,9 +1725,7 @@ pl_advance: !zone
|
||||
sta playerX
|
||||
ldy #0
|
||||
beq .done
|
||||
.ok ; Not blocked. Render at the new position
|
||||
jsr renderFrame
|
||||
; See if we're in a new map tile.
|
||||
.ok ; Not blocked. See if we're in a new map tile.
|
||||
pla
|
||||
eor playerY+1
|
||||
sta tmp
|
||||
@ -1741,39 +1736,26 @@ pl_advance: !zone
|
||||
tay
|
||||
pla
|
||||
tya
|
||||
beq .done ; if not a new map tile, return zero
|
||||
; It is a new position. Is script hint set?
|
||||
bne +
|
||||
iny ; not a new map tile, return 1
|
||||
bne .done ; always taken
|
||||
+ ; It is a new map tile. Is script hint set?
|
||||
ldy playerX+1
|
||||
lda (pMap),y
|
||||
ldy #1 ; ret val 1 = new blk but no script
|
||||
ldy #2 ; ret val 2 = new blk but no script
|
||||
and #$20 ; map flag $20 is the script hint
|
||||
beq .done ; if not scripted, return one
|
||||
iny ; else return 2 = new blk and a script
|
||||
.done pla
|
||||
tax ; restore PLASMA eval stk pos
|
||||
dex ; make room for return value
|
||||
tya ; retrieve ret value
|
||||
sta evalStkL,x ; and store it
|
||||
lda #0
|
||||
sta evalStkH,x ; hi byte of return is zero
|
||||
bit setLcRW+lcBank2 ; switch PLASMA runtime back in
|
||||
rts ; and return to PLASMA
|
||||
iny ; else return 3 = new blk and a script
|
||||
.done tya ; retrieve ret value
|
||||
ldy #0 ; hi byte of ret is always 0
|
||||
rts ; all done
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Render at the current position and direction.
|
||||
; Params: none
|
||||
; Return: none
|
||||
pl_render: !zone
|
||||
txa
|
||||
pha ; save PLASMA eval stk pos
|
||||
bit setROM ; switch out PLASMA while we work
|
||||
jsr renderFrame
|
||||
pla
|
||||
tax ; restore PLASMA eval stk pos
|
||||
jsr prbyte
|
||||
jsr crout
|
||||
bit setLcRW+lcBank2 ; switch PLASMA runtime back in
|
||||
rts ; and return to PLASMA
|
||||
jmp renderFrame
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; Cast all the rays from the current player coord
|
||||
@ -1934,12 +1916,16 @@ copyScreen: !zone
|
||||
;-------------------------------------------------------------------------------
|
||||
; Called by PLASMA code to get the position on the map.
|
||||
; Parameters: @x, @y
|
||||
; Returns: Nothing
|
||||
; Returns: Nothing (but stores into the addressed variables)
|
||||
pl_getPos: !zone {
|
||||
lda playerY+1
|
||||
sec
|
||||
sbc #1 ; adjust for border guards
|
||||
jsr .sto
|
||||
inx
|
||||
lda playerX+1
|
||||
sec
|
||||
sbc #1 ; adjust for border guards
|
||||
; Now fall thru, and exit with X incremented once (2 params - 1 return slot = 1)
|
||||
.sto ldy evalStkL,x
|
||||
sty pTmp
|
||||
@ -1958,14 +1944,16 @@ pl_getPos: !zone {
|
||||
; Parameters: x, y
|
||||
; Returns: Nothing
|
||||
pl_setPos: !zone {
|
||||
lda evalStkL,x
|
||||
clc
|
||||
adc #1 ; adjust for border guards
|
||||
sta playerY+1
|
||||
lda evalStkL+1,x
|
||||
clc
|
||||
adc #1 ; adjust for border guards
|
||||
sta playerX+1
|
||||
lda #$80
|
||||
sta playerY
|
||||
sta playerX
|
||||
inx ; 2 params - 1 ret = +1
|
||||
rts
|
||||
}
|
||||
|
||||
@ -1975,10 +1963,7 @@ pl_setPos: !zone {
|
||||
; Returns: Nothing
|
||||
pl_getDir: !zone {
|
||||
lda playerDir
|
||||
dex
|
||||
sta evalStkL,x
|
||||
lda #0
|
||||
sta evalStkH,x
|
||||
ldy #0
|
||||
rts
|
||||
}
|
||||
|
||||
@ -1987,35 +1972,28 @@ pl_getDir: !zone {
|
||||
; Parameters: dir (0-15)
|
||||
; Returns: Nothing
|
||||
pl_setDir: !zone {
|
||||
lda evalStkL,x
|
||||
and #15
|
||||
sta playerDir
|
||||
dex ; 0 param - 1 ret = -1
|
||||
rts
|
||||
}
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
pl_setColor: !zone
|
||||
lda evalStkL,x ; color number
|
||||
tay
|
||||
lda skyGndTbl2,y
|
||||
pha
|
||||
lda skyGndTbl1,y
|
||||
pha
|
||||
lda evalStkH,x ; slot
|
||||
and #15
|
||||
tay ; color number
|
||||
lda evalStkL+1,x
|
||||
and #1
|
||||
asl
|
||||
tay
|
||||
pla
|
||||
sta skyColorEven,y
|
||||
pla
|
||||
sta skyColorOdd,y
|
||||
inx ; toss unused stack slot (parms=2, ret=1, diff=1)
|
||||
tax ; slot
|
||||
lda skyGndTbl1,y
|
||||
sta skyColorEven,x
|
||||
lda skyGndTbl2,y
|
||||
sta skyColorOdd,x
|
||||
rts
|
||||
|
||||
;-------------------------------------------------------------------------------
|
||||
; The real action
|
||||
pl_initMap: !zone
|
||||
; Figure out PLASMA stack for calling script init
|
||||
txa
|
||||
clc
|
||||
adc #5 ; 5 params
|
||||
@ -2030,7 +2008,6 @@ pl_initMap: !zone
|
||||
inx
|
||||
jsr pl_setPos
|
||||
; Proceed with loading
|
||||
bit setROM ; switch out PLASMA runtime while we work
|
||||
jsr loadTextures
|
||||
jsr copyScreen
|
||||
lda tablesInitted
|
||||
@ -2045,11 +2022,7 @@ pl_initMap: !zone
|
||||
sta tablesInitted
|
||||
jsr setExpansionCaller
|
||||
jsr graphInit
|
||||
jsr renderFrame
|
||||
bit setLcRW+lcBank2 ; switch PLASMA runtime back in
|
||||
ldx plasmaStk ; restore PLASMA's eval stk pos
|
||||
dex ; make room for dummy return (inc'd over params earlier)
|
||||
rts
|
||||
jmp renderFrame
|
||||
|
||||
; Following are log/pow lookup tables. For speed, align them on a page boundary.
|
||||
!align 255,0
|
||||
|
@ -220,10 +220,9 @@ LOAD_TILESET
|
||||
;----------------------------------------------------------------------
|
||||
; >> GET TILE IN CARDINAL DIRECTION AND FLAGS
|
||||
; (Returns Tile # in Y, Flags in A)
|
||||
; Each tile in memory can be 0-32, the flags are the upper 3 bits
|
||||
; 0 0 0
|
||||
; | | `- Visible obstruction (Can not see behind it)
|
||||
; | `--- Boundary (Can not walk on it)
|
||||
; Each tile in memory can be 0-63, the flags are the upper 2 bits
|
||||
; 0 0
|
||||
; | `--- Obstructed / Boundary (Can not walk on it)
|
||||
; `----- Script assigned, triggers script lookup
|
||||
;----------------------------------------------------------------------
|
||||
; >> SET X,Y COORDINATES FOR VIEWPORT CENTER
|
||||
@ -841,15 +840,16 @@ ROW_OFFSET = 3
|
||||
LDA #>emptyTile+1
|
||||
BNE .store_src ; always taken
|
||||
.not_empty
|
||||
; Calculate location of tile data == tile_base + (((tile & 31) - 1) * 32)
|
||||
; Calculate location of tile data == tile_base + (((tile & 63) - 1) * 32)
|
||||
LDY #0
|
||||
STY TILE_SOURCE+1
|
||||
AND #31
|
||||
AND #63
|
||||
SEC
|
||||
SBC #1 ; tile map is 1-based, tile set indexes are 0-based
|
||||
ASL
|
||||
ASL
|
||||
ASL
|
||||
ROL TILE_SOURCE+1
|
||||
ASL
|
||||
ROL TILE_SOURCE+1
|
||||
ASL
|
||||
@ -1289,8 +1289,7 @@ ADVANCE: !zone {
|
||||
|
||||
JSR CALC
|
||||
LDA AVATAR_TILE ; get tile flags
|
||||
AND #$40 ; obstructed?
|
||||
BEQ +
|
||||
BPL + ; no hi bit = not obstructed
|
||||
|
||||
; Player moved to an obstructed place. Undo!
|
||||
LDA AVATAR_DIR
|
||||
@ -1314,7 +1313,7 @@ ADVANCE: !zone {
|
||||
BEQ .ret
|
||||
INY ; moved
|
||||
LDA AVATAR_TILE
|
||||
AND #$20 ; check script flag
|
||||
AND #$40 ; check script flag
|
||||
BEQ .ret
|
||||
INY ; moved and also new place is scripted
|
||||
.ret RTS
|
||||
|
Loading…
Reference in New Issue
Block a user