Re-introduce prototype code
This commit is contained in:
77
src/game/Game.cs
Normal file
77
src/game/Game.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
namespace GameJamDungeon;
|
||||
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
public interface IGame : IProvide<IGameRepo>, INode3D
|
||||
{
|
||||
}
|
||||
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class Game : Node3D, IGame
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
|
||||
IGameRepo IProvide<IGameRepo>.Value() => GameRepo;
|
||||
|
||||
public IGameLogic GameLogic { get; set; } = default!;
|
||||
|
||||
public IGameRepo GameRepo { get; set; } = default!;
|
||||
|
||||
public GameLogic.IBinding GameBinding { get; set; } = default!;
|
||||
|
||||
[Dependency] public IAppRepo AppRepo => this.DependOn<IAppRepo>();
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
GameRepo = new GameRepo();
|
||||
GameLogic = new GameLogic();
|
||||
GameLogic.Set(GameRepo);
|
||||
GameLogic.Set(AppRepo);
|
||||
}
|
||||
|
||||
public void OnResolved()
|
||||
{
|
||||
GameBinding = GameLogic.Bind();
|
||||
GameBinding
|
||||
.Handle((in GameLogic.Output.StartGame _) => { GameRepo.Resume(); })
|
||||
.Handle((in GameLogic.Output.SetPauseMode output) => { CallDeferred(nameof(SetPauseMode), output.IsPaused); })
|
||||
.Handle((in GameLogic.Output.GameOver _) => { AppRepo.OnGameOver(); });
|
||||
|
||||
GameLogic.Input(new GameLogic.Input.Initialize());
|
||||
|
||||
this.Provide();
|
||||
GameLogic.Start();
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
{
|
||||
if (Input.IsActionJustPressed(GameInputs.Inventory))
|
||||
{
|
||||
GD.Print("Inventory button pressed");
|
||||
GameLogic.Input(new GameLogic.Input.InventoryMenuButtonPressed());
|
||||
}
|
||||
|
||||
if (Input.IsActionJustPressed(GameInputs.MiniMap))
|
||||
{
|
||||
GD.Print("MiniMap button pressed");
|
||||
GameLogic.Input(new GameLogic.Input.MiniMapButtonPressed());
|
||||
}
|
||||
|
||||
if (Input.IsActionJustReleased(GameInputs.MiniMap))
|
||||
{
|
||||
GD.Print("MiniMap button released");
|
||||
GameLogic.Input(new GameLogic.Input.MiniMapButtonReleased());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPauseMode(bool isPaused)
|
||||
{
|
||||
if (GetTree() != null)
|
||||
GetTree().Paused = isPaused;
|
||||
}
|
||||
|
||||
public void OnStart() => GameLogic.Input(new GameLogic.Input.Start());
|
||||
}
|
||||
83
src/game/Game.tscn
Normal file
83
src/game/Game.tscn
Normal file
@@ -0,0 +1,83 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://33ek675mfb5n"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/game/Game.cs" id="1_ytcii"]
|
||||
[ext_resource type="PackedScene" uid="uid://wg25dg65ksgg" path="res://src/map/dungeon/DungeonGenerator.tscn" id="2_cgboj"]
|
||||
[ext_resource type="PackedScene" uid="uid://cfecvvav8kkp6" path="res://src/player/Player.tscn" id="3_kk6ly"]
|
||||
[ext_resource type="PackedScene" uid="uid://dhpwwqow1ahrc" path="res://src/map/dungeon/rooms/Room1.tscn" id="4_56rmd"]
|
||||
[ext_resource type="Script" path="res://addons/SimpleDungeons/DungeonRoom3D.gd" id="5_5i8m5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bn4gslp2gk8ds" path="res://src/map/dungeon/corridor/Corridor.tscn" id="6_5fcqc"]
|
||||
|
||||
[sub_resource type="Environment" id="Environment_fke5g"]
|
||||
|
||||
[node name="Game" type="Node3D"]
|
||||
script = ExtResource("1_ytcii")
|
||||
|
||||
[node name="DungeonGenerator3D" parent="." instance=ExtResource("2_cgboj")]
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="DungeonGenerator3D"]
|
||||
environment = SubResource("Environment_fke5g")
|
||||
|
||||
[node name="Player" parent="DungeonGenerator3D" instance=ExtResource("3_kk6ly")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.55927, -4.70883, 25.1515)
|
||||
|
||||
[node name="RoomsContainer" type="Node3D" parent="DungeonGenerator3D"]
|
||||
|
||||
[node name="DungeonRoom3D_0" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_56rmd")]
|
||||
transform = Transform3D(1.19249e-08, 0, -1, 0, 1, 0, 1, 0, 1.19249e-08, 5, 0, 15)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="DungeonRoom3D_1" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("4_56rmd")]
|
||||
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, -25, 0, -25)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_2" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 15)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_3" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 5)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_4" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, -5)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_5" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, -15)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_6" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, -15)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_7" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, -25)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_8" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 0, 15)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_9" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 0, 5)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_10" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 5)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_11" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -35, 0, -25)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_12" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -35, 0, -15)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_13" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -25, 0, -15)
|
||||
script = ExtResource("5_5i8m5")
|
||||
|
||||
[node name="Corridor_14" type="Node3D" parent="DungeonGenerator3D/RoomsContainer" instance=ExtResource("6_5fcqc")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 0, -5)
|
||||
script = ExtResource("5_5i8m5")
|
||||
10
src/game/GameData.cs
Normal file
10
src/game/GameData.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Chickensoft.Introspection;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
[Meta, Id("game_data")]
|
||||
public partial record GameData
|
||||
{
|
||||
public required PlayerData PlayerData { get; init; }
|
||||
}
|
||||
}
|
||||
20
src/game/GameLogic.Input.cs
Normal file
20
src/game/GameLogic.Input.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public static class Input
|
||||
{
|
||||
public readonly record struct Start;
|
||||
|
||||
public readonly record struct Initialize;
|
||||
|
||||
public readonly record struct InventoryMenuButtonPressed;
|
||||
|
||||
public readonly record struct MiniMapButtonPressed;
|
||||
|
||||
public readonly record struct MiniMapButtonReleased;
|
||||
|
||||
public readonly record struct GameOver;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/game/GameLogic.Output.cs
Normal file
24
src/game/GameLogic.Output.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public static class Output
|
||||
{
|
||||
public readonly record struct StartGame();
|
||||
|
||||
public readonly record struct SetInventoryMode(List<InventoryItem> Inventory);
|
||||
|
||||
public readonly record struct HideInventory();
|
||||
|
||||
public readonly record struct SetPauseMode(bool IsPaused);
|
||||
|
||||
public readonly record struct ShowMiniMap();
|
||||
|
||||
public readonly record struct HideMiniMap();
|
||||
|
||||
public readonly record struct GameOver();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/game/GameLogic.State.cs
Normal file
28
src/game/GameLogic.State.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
[Meta]
|
||||
public abstract partial record State : StateLogic<State>
|
||||
{
|
||||
protected State()
|
||||
{
|
||||
OnAttach(() =>
|
||||
{
|
||||
var gameRepo = Get<IGameRepo>();
|
||||
gameRepo.IsPaused.Sync += OnIsPaused;
|
||||
});
|
||||
OnDetach(() =>
|
||||
{
|
||||
var gameRepo = Get<IGameRepo>();
|
||||
gameRepo.IsPaused.Sync -= OnIsPaused;
|
||||
});
|
||||
}
|
||||
|
||||
public void OnIsPaused(bool isPaused) => Output(new Output.SetPauseMode(isPaused));
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/game/GameLogic.cs
Normal file
14
src/game/GameLogic.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public interface IGameLogic : ILogicBlock<GameLogic.State>;
|
||||
|
||||
[Meta]
|
||||
[LogicBlock(typeof(State), Diagram = true)]
|
||||
public partial class GameLogic : LogicBlock<GameLogic.State>, IGameLogic
|
||||
{
|
||||
public override Transition GetInitialState() => To<State.MenuBackdrop>();
|
||||
}
|
||||
}
|
||||
98
src/game/IGameRepo.cs
Normal file
98
src/game/IGameRepo.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using Chickensoft.Collections;
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public interface IGameRepo : IDisposable
|
||||
{
|
||||
event Action? Ended;
|
||||
|
||||
IAutoProp<List<InventoryItem>> InventoryItems { get; }
|
||||
|
||||
IAutoProp<bool> IsInventoryScreenOpened { get; }
|
||||
|
||||
IAutoProp<bool> IsPaused { get; }
|
||||
|
||||
void Pause();
|
||||
|
||||
void Resume();
|
||||
|
||||
IAutoProp<Vector3> PlayerGlobalPosition { get; }
|
||||
|
||||
void SetPlayerGlobalPosition(Vector3 playerGlobalPosition);
|
||||
}
|
||||
|
||||
public class GameRepo : IGameRepo
|
||||
{
|
||||
public event Action? Ended;
|
||||
|
||||
private readonly AutoProp<List<InventoryItem>> _inventoryItems;
|
||||
private readonly AutoProp<bool> _isInventoryScreenOpened;
|
||||
|
||||
public IAutoProp<List<InventoryItem>> InventoryItems => _inventoryItems;
|
||||
|
||||
public IAutoProp<bool> IsInventoryScreenOpened => _isInventoryScreenOpened;
|
||||
|
||||
public IAutoProp<Vector3> PlayerGlobalPosition => _playerGlobalPosition;
|
||||
private readonly AutoProp<Vector3> _playerGlobalPosition;
|
||||
|
||||
public IAutoProp<bool> IsPaused => _isPaused;
|
||||
private readonly AutoProp<bool> _isPaused;
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
public GameRepo()
|
||||
{
|
||||
_inventoryItems = new AutoProp<List<InventoryItem>>([]);
|
||||
_isInventoryScreenOpened = new AutoProp<bool>(false);
|
||||
_isPaused = new AutoProp<bool>(false);
|
||||
_playerGlobalPosition = new AutoProp<Vector3>(Vector3.Zero);
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
_isPaused.OnNext(true);
|
||||
GD.Print("Paused");
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
_isPaused.OnNext(false);
|
||||
GD.Print("Resume");
|
||||
}
|
||||
|
||||
public void SetPlayerGlobalPosition(Vector3 playerGlobalPosition) => _playerGlobalPosition.OnNext(playerGlobalPosition);
|
||||
|
||||
public void OnGameEnded()
|
||||
{
|
||||
Pause();
|
||||
Ended?.Invoke();
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_playerGlobalPosition.OnCompleted();
|
||||
_playerGlobalPosition.Dispose();
|
||||
_inventoryItems.OnCompleted();
|
||||
_inventoryItems.Dispose();
|
||||
_isInventoryScreenOpened.OnCompleted();
|
||||
_isInventoryScreenOpened.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/game/state/states/GameLogic.State.Quit.cs
Normal file
20
src/game/state/states/GameLogic.State.Quit.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record Quit : State
|
||||
{
|
||||
public Quit()
|
||||
{
|
||||
this.OnEnter(() => Output(new Output.GameOver()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/game/state/states/InventoryOpened.cs
Normal file
22
src/game/state/states/InventoryOpened.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record InventoryOpened : State, IGet<Input.InventoryMenuButtonPressed>
|
||||
{
|
||||
public InventoryOpened()
|
||||
{
|
||||
this.OnEnter(() => { Get<IGameRepo>().Pause(); Output(new Output.SetInventoryMode(Get<IGameRepo>().InventoryItems.Value)); });
|
||||
this.OnExit(() => { Output(new Output.HideInventory()); });
|
||||
}
|
||||
public Transition On(in Input.InventoryMenuButtonPressed input) => To<Playing>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/game/state/states/MenuBackdrop.cs
Normal file
29
src/game/state/states/MenuBackdrop.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Chickensoft.Introspection;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record MenuBackdrop : State, IGet<Input.Start>, IGet<Input.Initialize>
|
||||
{
|
||||
public MenuBackdrop()
|
||||
{
|
||||
OnAttach(() => Get<IAppRepo>().GameEntered += OnGameEntered);
|
||||
OnDetach(() => Get<IAppRepo>().GameEntered -= OnGameEntered);
|
||||
}
|
||||
|
||||
public void OnGameEntered() => Input(new Input.Start());
|
||||
|
||||
public Transition On(in Input.Start input) => To<Playing>();
|
||||
|
||||
public Transition On(in Input.Initialize input)
|
||||
{
|
||||
return ToSelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/game/state/states/MiniMapOpen.cs
Normal file
23
src/game/state/states/MiniMapOpen.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record MinimapOpen : State, IGet<Input.MiniMapButtonReleased>
|
||||
{
|
||||
public MinimapOpen()
|
||||
{
|
||||
this.OnEnter(() => { Get<IGameRepo>().Pause(); Output(new Output.ShowMiniMap()); });
|
||||
this.OnExit(() => { Output(new Output.HideMiniMap()); });
|
||||
}
|
||||
|
||||
public Transition On(in Input.MiniMapButtonReleased input) => To<Playing>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/game/state/states/Paused.cs
Normal file
26
src/game/state/states/Paused.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record Paused : State, IGet<Input.InventoryMenuButtonPressed>, IGet<Input.MiniMapButtonReleased>
|
||||
{
|
||||
public Paused()
|
||||
{
|
||||
this.OnEnter(() => Get<IGameRepo>().Pause());
|
||||
this.OnExit(() => Output(new Output.SetPauseMode(false)));
|
||||
}
|
||||
|
||||
|
||||
public virtual Transition On(in Input.InventoryMenuButtonPressed input) => To<Playing>();
|
||||
|
||||
public virtual Transition On(in Input.MiniMapButtonReleased input) => To<Playing>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/game/state/states/Playing.cs
Normal file
34
src/game/state/states/Playing.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace GameJamDungeon
|
||||
{
|
||||
public partial class GameLogic
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record Playing : State, IGet<Input.InventoryMenuButtonPressed>, IGet<Input.MiniMapButtonPressed>, IGet<Input.GameOver>
|
||||
{
|
||||
public Playing()
|
||||
{
|
||||
this.OnEnter(() => { Output(new Output.StartGame()); Get<IGameRepo>().Resume(); });
|
||||
|
||||
OnAttach(() => Get<IGameRepo>().Ended += OnEnded);
|
||||
OnDetach(() => Get<IGameRepo>().Ended -= OnEnded);
|
||||
}
|
||||
|
||||
public void OnEnded() => Input(new Input.GameOver());
|
||||
|
||||
public Transition On(in Input.InventoryMenuButtonPressed input) => To<InventoryOpened>();
|
||||
|
||||
public Transition On(in Input.MiniMapButtonPressed input) => To<MinimapOpen>();
|
||||
|
||||
public Transition On(in Input.GameOver input)
|
||||
{
|
||||
return To<Quit>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user