Lots of bugfixes - see notes
This commit is contained in:
@@ -476,7 +476,7 @@ class CollectionState(object):
|
|||||||
new_crystal_state = crystal_state
|
new_crystal_state = crystal_state
|
||||||
for exit in new_region.exits:
|
for exit in new_region.exits:
|
||||||
door = exit.door
|
door = exit.door
|
||||||
if door is not None and door.crystal == CrystalBarrier.Either:
|
if door is not None and door.crystal == CrystalBarrier.Either and door.entrance.can_reach(self):
|
||||||
new_crystal_state = CrystalBarrier.Either
|
new_crystal_state = CrystalBarrier.Either
|
||||||
break
|
break
|
||||||
if new_region in rrp:
|
if new_region in rrp:
|
||||||
@@ -487,7 +487,7 @@ class CollectionState(object):
|
|||||||
for exit in new_region.exits:
|
for exit in new_region.exits:
|
||||||
door = exit.door
|
door = exit.door
|
||||||
if door is not None and not door.blocked:
|
if door is not None and not door.blocked:
|
||||||
door_crystal_state = new_crystal_state & (door.crystal or CrystalBarrier.Either)
|
door_crystal_state = door.crystal if door.crystal else new_crystal_state
|
||||||
bc[exit] = door_crystal_state
|
bc[exit] = door_crystal_state
|
||||||
queue.append((exit, door_crystal_state))
|
queue.append((exit, door_crystal_state))
|
||||||
elif door is None:
|
elif door is None:
|
||||||
@@ -1906,7 +1906,8 @@ class Spoiler(object):
|
|||||||
'enemy_damage': self.world.enemy_damage,
|
'enemy_damage': self.world.enemy_damage,
|
||||||
'players': self.world.players,
|
'players': self.world.players,
|
||||||
'teams': self.world.teams,
|
'teams': self.world.teams,
|
||||||
'experimental' : self.world.experimental
|
'experimental': self.world.experimental,
|
||||||
|
'keydropshuffle': self.world.keydropshuffle,
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
@@ -1967,6 +1968,7 @@ class Spoiler(object):
|
|||||||
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
||||||
outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
||||||
outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
|
outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
|
||||||
|
outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
|
||||||
if self.doors:
|
if self.doors:
|
||||||
outfile.write('\n\nDoors:\n\n')
|
outfile.write('\n\nDoors:\n\n')
|
||||||
outfile.write('\n'.join(
|
outfile.write('\n'.join(
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ def link_doors(world, player):
|
|||||||
for entrance, ext in straight_staircases:
|
for entrance, ext in straight_staircases:
|
||||||
connect_two_way(world, entrance, ext, player)
|
connect_two_way(world, entrance, ext, player)
|
||||||
|
|
||||||
|
connect_custom(world, player)
|
||||||
|
|
||||||
find_inaccessible_regions(world, player)
|
find_inaccessible_regions(world, player)
|
||||||
|
|
||||||
if world.intensity[player] >= 3 and world.doorShuffle[player] in ['basic', 'crossed']:
|
if world.intensity[player] >= 3 and world.doorShuffle[player] in ['basic', 'crossed']:
|
||||||
@@ -197,6 +199,12 @@ def convert_key_doors(k_doors, world, player):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def connect_custom(world, player):
|
||||||
|
if hasattr(world, 'custom_doors') and world.custom_doors[player]:
|
||||||
|
for entrance, ext in world.custom_doors[player]:
|
||||||
|
connect_two_way(world, entrance, ext, player)
|
||||||
|
|
||||||
|
|
||||||
def connect_simple_door(world, exit_name, region_name, player):
|
def connect_simple_door(world, exit_name, region_name, player):
|
||||||
region = world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
world.get_entrance(exit_name, player).connect(region)
|
world.get_entrance(exit_name, player).connect(region)
|
||||||
|
|||||||
@@ -564,6 +564,8 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
|
|||||||
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
|
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
|
||||||
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
|
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
|
||||||
paths.append('Thieves Attic Window')
|
paths.append('Thieves Attic Window')
|
||||||
|
elif 'Thieves Attic Window' in all_r_names:
|
||||||
|
paths.append('Thieves Attic Window')
|
||||||
for boss in boss_path_checks:
|
for boss in boss_path_checks:
|
||||||
if boss in all_r_names:
|
if boss in all_r_names:
|
||||||
paths.append(boss)
|
paths.append(boss)
|
||||||
@@ -1260,6 +1262,9 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
|||||||
sector = find_sector(r_name, all_sectors)
|
sector = find_sector(r_name, all_sectors)
|
||||||
reverse_d_map[sector] = key
|
reverse_d_map[sector] = key
|
||||||
|
|
||||||
|
complete_dungeons = {x: y for x, y in dungeon_map.items() if sum(len(sector.outstanding_doors) for sector in y.sectors) <= 0}
|
||||||
|
[dungeon_map.pop(key) for key in complete_dungeons.keys()]
|
||||||
|
|
||||||
# categorize sectors
|
# categorize sectors
|
||||||
identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections,
|
identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections,
|
||||||
dungeon_entrances, split_dungeon_entrances)
|
dungeon_entrances, split_dungeon_entrances)
|
||||||
@@ -1315,6 +1320,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
|||||||
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builder_info)
|
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builder_info)
|
||||||
# the rest
|
# the rest
|
||||||
assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info)
|
assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info)
|
||||||
|
dungeon_map.update(complete_dungeons)
|
||||||
finished = True
|
finished = True
|
||||||
except NeutralizingException:
|
except NeutralizingException:
|
||||||
pass
|
pass
|
||||||
|
|||||||
20
ItemList.py
20
ItemList.py
@@ -6,7 +6,7 @@ from BaseClasses import Region, RegionType, Shop, ShopType, Location
|
|||||||
from Bosses import place_bosses
|
from Bosses import place_bosses
|
||||||
from Dungeons import get_dungeon_item_pool
|
from Dungeons import get_dungeon_item_pool
|
||||||
from EntranceShuffle import connect_entrance
|
from EntranceShuffle import connect_entrance
|
||||||
from Fill import FillError, fill_restrictive
|
from Fill import FillError, fill_restrictive, fast_fill
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
import source.classes.constants as CONST
|
import source.classes.constants as CONST
|
||||||
@@ -738,3 +738,21 @@ def test():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
test()
|
test()
|
||||||
|
|
||||||
|
|
||||||
|
def fill_specific_items(world):
|
||||||
|
keypool = [item for item in world.itempool if item.smallkey]
|
||||||
|
cage = world.get_location('Tower of Hera - Basement Cage', 1)
|
||||||
|
c_dungeon = cage.parent_region.dungeon
|
||||||
|
key_item = next(x for x in keypool if c_dungeon.name in x.name or (c_dungeon.name == 'Hyrule Castle' and 'Escape' in x.name))
|
||||||
|
world.itempool.remove(key_item)
|
||||||
|
all_state = world.get_all_state(True)
|
||||||
|
fill_restrictive(world, all_state, [cage], [key_item])
|
||||||
|
|
||||||
|
# somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria')
|
||||||
|
# shooter = world.get_location('Palace of Darkness - Shooter Room', 1)
|
||||||
|
# world.itempool.remove(somaria)
|
||||||
|
# all_state = world.get_all_state(True)
|
||||||
|
# fill_restrictive(world, all_state, [shooter], [somaria])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
7
Main.py
7
Main.py
@@ -21,10 +21,10 @@ from RoomData import create_rooms
|
|||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
|
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
|
||||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items
|
||||||
from Utils import output_path, parse_player_names
|
from Utils import output_path, parse_player_names
|
||||||
|
|
||||||
__version__ = '0.2.0.7-u'
|
__version__ = '0.2.0.8-u'
|
||||||
|
|
||||||
class EnemizerError(RuntimeError):
|
class EnemizerError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
@@ -139,6 +139,9 @@ def main(args, seed=None, fish=None):
|
|||||||
|
|
||||||
fill_prizes(world)
|
fill_prizes(world)
|
||||||
|
|
||||||
|
# used for debugging
|
||||||
|
# fill_specific_items(world)
|
||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
||||||
|
|
||||||
shuffled_locations = None
|
shuffled_locations = None
|
||||||
|
|||||||
@@ -3,14 +3,19 @@
|
|||||||
* Lobby shuffle added as Intensity level 3
|
* Lobby shuffle added as Intensity level 3
|
||||||
* Can now be found in the spoiler
|
* Can now be found in the spoiler
|
||||||
* Known issues:
|
* Known issues:
|
||||||
|
* If a dungeon is vanilla in ER and Sanc is in that dungeon and the dungeon has an entrance that needs to let link out: is broken.
|
||||||
|
* e.g. PoD, GT, TR
|
||||||
|
* Some TR lobbies that need a bomb aren't pre-opened.
|
||||||
* Palettes aren't perfect - may add Sanctuary and Sewer palette back. May add a way to turn off palette "fixing"
|
* Palettes aren't perfect - may add Sanctuary and Sewer palette back. May add a way to turn off palette "fixing"
|
||||||
* Certain hints in ER due to lobby changes
|
* Some ugly colors
|
||||||
|
* Invisible floors can be see in many palettes
|
||||||
* Animated tiles aren't loaded correctly in lobbies
|
* Animated tiles aren't loaded correctly in lobbies
|
||||||
* If a wallmaster grabs you and the lobby is dark, the lamp doesn't turn on
|
* If a wallmaster grabs you and the lobby is dark, the lamp doesn't turn on
|
||||||
* --keydropshuffle added (coming to the GUI soon). This add 33 new locations to the game where keys are found under pots
|
* --keydropshuffle added (coming to the GUI soon). This add 33 new locations to the game where keys are found under pots
|
||||||
and where enemies drop keys. This includes 32 small key location and the ball and chain guard who normally drop the HC
|
and where enemies drop keys. This includes 32 small key location and the ball and chain guard who normally drop the HC
|
||||||
Big Key.
|
Big Key.
|
||||||
* Multiworld untested - May need changes to MultiClient/MultiServer to recognize new locations
|
* Overall location count updated
|
||||||
|
* Setting mentioned in spoiler
|
||||||
* GT Big Key count / total location count needs to be updated
|
* GT Big Key count / total location count needs to be updated
|
||||||
* --mixed_travel setting added
|
* --mixed_travel setting added
|
||||||
* Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
|
* Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
|
||||||
@@ -30,6 +35,15 @@ otherwise unconnected logically can be reach using these glitches. To prevent th
|
|||||||
|
|
||||||
# Bug Fixes
|
# Bug Fixes
|
||||||
|
|
||||||
|
* 2.0.8-u
|
||||||
|
* Player sprite disappears after picking up a key drop in keydropshuffle
|
||||||
|
* Sewers and Hyrule Castle compass problems
|
||||||
|
* Double count of the Hera Basement Cage item (both overall and compass)
|
||||||
|
* Unnecessary/inconsistent rug cutoff
|
||||||
|
* TR Crystal Maze thought you get through backwards without Somaria
|
||||||
|
* Ensure Thieves Attic Window area can always be reached
|
||||||
|
* Fixed where HC big key was not counted
|
||||||
|
* Prior fixes
|
||||||
* Fixed a situation where logic did not account properly for Big Key doors in standard Hyrule Castle
|
* Fixed a situation where logic did not account properly for Big Key doors in standard Hyrule Castle
|
||||||
* Fixed a problem ER shuffle generation that did not account for lobbies moving around
|
* Fixed a problem ER shuffle generation that did not account for lobbies moving around
|
||||||
* Fixed a problem with camera unlock (GT Mimics and Mire Minibridge)
|
* Fixed a problem with camera unlock (GT Mimics and Mire Minibridge)
|
||||||
@@ -38,6 +52,7 @@ otherwise unconnected logically can be reach using these glitches. To prevent th
|
|||||||
|
|
||||||
# Known Issues
|
# Known Issues
|
||||||
|
|
||||||
(I'm planning to fix theese in this Unstable iteration hopefully)
|
* Multiworld = /missing command not working
|
||||||
|
* Potenial keylocks in multi-entrance dungeons
|
||||||
* Backward TR Crystal Maze locking Somaria
|
* Incorrect vanilla keylogic for Mire
|
||||||
|
* ER - Potential for Skull Woods West to be completely inaccessible in non-beatable logic
|
||||||
22
Rom.py
22
Rom.py
@@ -24,7 +24,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '78947c3825898cac3ab57cbe44b50390'
|
RANDOMIZERBASEHASH = 'fb2886fc00a7736369ce6ba90b526bc9'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -709,8 +709,21 @@ def patch_rom(world, rom, player, team, enemized):
|
|||||||
|
|
||||||
write_custom_shops(rom, world, player)
|
write_custom_shops(rom, world, player)
|
||||||
|
|
||||||
|
def credits_digit(num):
|
||||||
|
# top: $54 is 1, 55 2, etc , so 57=4, 5C=9
|
||||||
|
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
|
||||||
|
return 0x53+num, 0x79+num
|
||||||
|
|
||||||
if world.keydropshuffle[player]:
|
if world.keydropshuffle[player]:
|
||||||
rom.write_byte(0x140000, 1)
|
rom.write_byte(0x140000, 1)
|
||||||
|
mid_top, mid_bot = credits_digit(4)
|
||||||
|
last_top, last_bot = credits_digit(9)
|
||||||
|
# top half
|
||||||
|
rom.write_byte(0x118C53, mid_top)
|
||||||
|
rom.write_byte(0x118C54, last_top)
|
||||||
|
# bottom half
|
||||||
|
rom.write_byte(0x118C71, mid_bot)
|
||||||
|
rom.write_byte(0x118C72, last_bot)
|
||||||
|
|
||||||
# patch medallion requirements
|
# patch medallion requirements
|
||||||
if world.required_medallions[player][0] == 'Bombos':
|
if world.required_medallions[player][0] == 'Bombos':
|
||||||
@@ -2210,11 +2223,18 @@ def patch_shuffled_dark_sanc(world, rom, player):
|
|||||||
|
|
||||||
def update_compasses(rom, world, player):
|
def update_compasses(rom, world, player):
|
||||||
layouts = world.dungeon_layouts[player]
|
layouts = world.dungeon_layouts[player]
|
||||||
|
provided_dungeon = False
|
||||||
for name, builder in layouts.items():
|
for name, builder in layouts.items():
|
||||||
dungeon_id = compass_data[name][4]
|
dungeon_id = compass_data[name][4]
|
||||||
rom.write_byte(0x187000 + dungeon_id//2, builder.location_cnt)
|
rom.write_byte(0x187000 + dungeon_id//2, builder.location_cnt)
|
||||||
if builder.bk_provided:
|
if builder.bk_provided:
|
||||||
|
if provided_dungeon:
|
||||||
|
logging.getLogger('').warning('Multiple dungeons have forced BKs! Compass code might need updating?')
|
||||||
rom.write_byte(0x186FFF, dungeon_id)
|
rom.write_byte(0x186FFF, dungeon_id)
|
||||||
|
provided_dungeon = True
|
||||||
|
if not provided_dungeon:
|
||||||
|
rom.write_byte(0x186FFF, 0xff)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main',
|
InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main',
|
||||||
|
|||||||
@@ -139,6 +139,14 @@ org $019dbd ; <- Bank01.asm : 4465 of Object_Draw8xN (LDA $9B52, Y : STA $7E2000
|
|||||||
jsl CutoffEntranceRug : bra .nextTile : nop
|
jsl CutoffEntranceRug : bra .nextTile : nop
|
||||||
.nextTile
|
.nextTile
|
||||||
|
|
||||||
|
;maybe set 02e2 to 0
|
||||||
|
|
||||||
|
org $0799de ; <- Bank07.asm : 4088 (LDA.b #$15 : STA $5D)
|
||||||
|
JSL StoreTempBunnyState
|
||||||
|
;
|
||||||
|
org $08c450 ; <- ancilla_receive_item.asm : 146-148 (STY $5D : STZ $02D8)
|
||||||
|
JSL RetrieveBunnyState : NOP
|
||||||
|
|
||||||
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
|
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
|
||||||
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
|
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
|
||||||
; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds
|
; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds
|
||||||
|
|||||||
@@ -116,10 +116,12 @@ KeyGet:
|
|||||||
lda $7ef36f ; what we wrote over
|
lda $7ef36f ; what we wrote over
|
||||||
pha
|
pha
|
||||||
lda.l ShuffleKeyDrops : bne +
|
lda.l ShuffleKeyDrops : bne +
|
||||||
pla : rtl
|
- pla : rtl
|
||||||
+
|
+ ldy $0e80, x
|
||||||
ldy $0e80, x
|
lda $a0 : cmp #$87 : bne +
|
||||||
phy
|
jsr ShouldKeyBeCountedForDungeon : bcc -
|
||||||
|
jsl CountChestKeyLong : bra -
|
||||||
|
+ phy
|
||||||
jsr KeyGetPlayer : sta !MULTIWORLD_ITEM_PLAYER_ID
|
jsr KeyGetPlayer : sta !MULTIWORLD_ITEM_PLAYER_ID
|
||||||
jsl.l $0791b3 ; Player_HaltDashAttackLong
|
jsl.l $0791b3 ; Player_HaltDashAttackLong
|
||||||
jsl.l Link_ReceiveItem
|
jsl.l Link_ReceiveItem
|
||||||
@@ -136,6 +138,16 @@ KeyGet:
|
|||||||
pla : dec : rtl
|
pla : dec : rtl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; Input Y - the item type
|
||||||
|
ShouldKeyBeCountedForDungeon:
|
||||||
|
{
|
||||||
|
phx
|
||||||
|
lda $040c : lsr : tax
|
||||||
|
tya : cmp KeyTable, x : bne +
|
||||||
|
- plx : sec : rts
|
||||||
|
+ cmp #$24 : beq -
|
||||||
|
plx : clc : rts
|
||||||
|
}
|
||||||
|
|
||||||
BigKeyGet:
|
BigKeyGet:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ SuctionOverworldFix:
|
|||||||
CutoffRooms:
|
CutoffRooms:
|
||||||
db $bc, $a2, $1a, $49, $14, $8c, $9f, $c2
|
db $bc, $a2, $1a, $49, $14, $8c, $9f, $c2
|
||||||
db $66, $5d, $a8
|
db $66, $5d, $a8
|
||||||
|
; Don't forget CutoffRoomCount!!!
|
||||||
|
|
||||||
CutoffEntranceRug:
|
CutoffEntranceRug:
|
||||||
pha : phx
|
pha : phx
|
||||||
@@ -96,7 +97,7 @@ CutoffEntranceRug:
|
|||||||
cmp #$000C : bne .norm
|
cmp #$000C : bne .norm
|
||||||
+ lda $a0 : sep #$20 : ldx #$0000
|
+ lda $a0 : sep #$20 : ldx #$0000
|
||||||
- cmp.l CutoffRooms, x : beq .check
|
- cmp.l CutoffRooms, x : beq .check
|
||||||
inx : cpx #$0009 : !blt - ; CutoffRoom Count is here!
|
inx : cpx #$000B : !blt - ; CutoffRoomCount is here!
|
||||||
rep #$20
|
rep #$20
|
||||||
.norm plx : pla : lda $9B52, y : sta $7E2000, x ; what we wrote over
|
.norm plx : pla : lda $9B52, y : sta $7E2000, x ; what we wrote over
|
||||||
rtl
|
rtl
|
||||||
@@ -108,3 +109,15 @@ rtl
|
|||||||
bra .norm
|
bra .norm
|
||||||
.skip plx : pla : rtl
|
.skip plx : pla : rtl
|
||||||
|
|
||||||
|
|
||||||
|
StoreTempBunnyState:
|
||||||
|
LDA $5D : CMP #$1C : BNE +
|
||||||
|
STA $5F
|
||||||
|
+ LDA #$15 : STA $5D ; what we wrote over
|
||||||
|
RTL
|
||||||
|
|
||||||
|
RetrieveBunnyState:
|
||||||
|
STY $5D : STZ $02D8 ; what we wrote over
|
||||||
|
LDA $5F : BEQ +
|
||||||
|
STA $5D
|
||||||
|
+ RTL
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user